Fields in Traits

I think that two traits actually makes the problem worse. I don't want to allow partial borrows between traits[1]. Otherwise we need a complicated way of defining what traits can be used together. My intention in that example was that a single trait could define a subset of itself that could be borrowed. So you could borrow ViewA independently of ViewB. This makes the following code legal.

let foo: Foo = unimplemented!();
let borrow1 = foo.first();
let borrow2 = foo.second();

In current Rust (assuming that first and second have the signature Fn(&self) -> &Thing) this would be illegal because you are borrowing from foo twice, and one is mutable.

[1] There might need to be a story of this with "related" traits like a trait that requires another to be implemented.

How how do you specify that? And how do you document it for your users? This is what a named view offers, basically -- a way to document it that is not "here is a list of private fields that you should not know about; now check if they intersect one another". (And, even better, by giving names to the views, potentially a way to assert that they ought to be disjoint.)

I agree that those are distinct problems, but I am not yet convinced that they want to be distinct language mechanisms. To be honest, I think that the performance aspects (which have to do with how we will compile code that is using a trait object that contains fields) and this other concept are pretty much orthogonal.

Yes, this is sort of the 'new thing'. I'm not sure I would characterize it as "complicated", but it seems like something we would have to add.

Maybe it's just a layer on top of the core system -- that would be nice. I have to get a bit more crisp in my thinking about how views would interact with the borrow checker, and in particular what it might mean to have "public views".

Everything here also intersects the this question I raised earlier, which isn't specific to views -- how smart would the borrow checker be with understanding disjointness?

So I think what I am trying to express is that I think we need something to express these borrows (which I think you agree with), but binding them directly to fields feels too restrictive to me. I think that we would want to have something more abstract like the views you were describing.

However these features seem to make the "fields in traits" idea a little useless since this covers 1 and 2. Maybe we could consider some simple syntax sugar for mapping get/get_mut to a field with an appropriate view.

I really like this idea. But what about introducing more sugaring to it? So it feels more like a "view". I imagined something like this:

view CustomAccess for MyStruct {
    let field_a: A = self.field_a;
    ...
}

// This is basically sugar for:
// trait CustomAccess {
//     let field_a: A;
// }
//
// impl CustomAccess for MyStruct {
//     let field_a = self.field_a;
// }

A "view" then should probably be a restrictive subset of a trait/impl/field-in-trait (only fields are allowed).

1 Like

TLDR at the end;

My two cents on the matter, I do not think Traits should or realistically could include fields because:

  • It forces the user of a trait to maintain a field they may not want (I’ll explain forces later)
  • Traits already effectively have this power without forcing it upon the user.
  • Trait fields would either break the current implementation of Traits or force overhead on users.

Example of what I’m talking about:

pub struct Foo {
    x: usize
}

pub trait Bar: BorrowMut<Foo> {
    fn do_something_cool_with_foo(&mut self);
    fn do_other_thing_with_foo(&mut self) {
        //Your awesome implementation.
    }
}

impl<T> Bar for T where T: BorrowMut<Foo> {}

This is valid for Traits as they currently are and, if implemented in a library crate, the functionality of Bar is provided for all types which choose to implement BorrowMut<Foo> implicitly with little to no overhead.

This implementation also effectively implements “traits with fields” by dint of the fact that before a user can make use of the Bar trait for their MyType type they must first provide some implementation to treat MyType as if it had the data of Foo.

However if Traits themselves could be made with fields either:

  • Every type that wants to make use of the Bar trait would have to implement each of the fields currently in Foo in some way, requiring they think and understand how the data should be managed.
  • The Bar trait provides implementation for initialising and managing the data already and the line “impl<T> Bar for T where T: BorrowMut<Foo> {}” would implement Bar on all types which make use of it without necessarily telling the user that there’s a memory overhead added to their MyType by using Bar or otherwise cloging up their compiler output with warnings to let them know its happening.

TLDR: Traits as they currently exist alongside Rusts borrowing can already implement “traits with fields” in an effective and user friendly way that requires a conscious decision by the user to take responsibility for any memory overhead.

1 Like

This looks really promising (so I hope that it moves forward). However, there is something that is missing from this (or even view traits) as apposed to getters/setters (and how they are used in languages like C#) is that with getters/ setters you can define bounds and checks where as with this you cannot.

I think that the ability to return a result from a setter as very cool and succinct.

example:

if (foo.bar = 15)? > 10 { ... }

This would try and assign 15 to the bar field in foo and then check to see if it above 10

I would really hate if an assignment to a field (or even the seemingly read-only access thereof) could now invoke arbitrary functions. The “setter returns a value” way is an extension of this and I find it even more convoluted. I would not ever want to have to read any code using this sort of feature.

2 Likes

What's wrong with doing it the old-fashioned way - through a function call?

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.