Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow fields in traits that map to lvalues in impl'ing type #1546

Closed
wants to merge 5 commits into from

Conversation

nikomatsakis
Copy link
Contributor

@nikomatsakis nikomatsakis commented Mar 16, 2016

UPDATE: The finishing touches and final conversation on this RFC are taking place in a dedicated repository, as described in this comment.


The primary change proposed here is to allow fields within traits and then permit access to those fields within generic functions and from trait objects:

  • trait Trait { field: usize }
  • Implementing type can "direct" this field to any lvalue within itself
  • All fields within a trait or its supertraits must be mapped to
    disjoint locations

Fields serve as a better alternative to accessor functions in traits. They are more compatible with Rust's safety checks than accessors, but also more efficient when using trait objects.

Many of the ideas here were originally proposed in #250 in some form. As such, they represent an important "piece of the puzzle" towards solving #349.

cc @eddyb @aturon @rust-lang/lang

Rendered view.

Planned edits

  • Adopt let syntax for declaring fields in traits and impls. (decided against this)
  • Clarify that there are "private type in public API" rules that apply to the type of a field.
  • Decide whether mut is required for mutable access. (At minimum, add as an unresolved question.)
  • Typo

@nikomatsakis nikomatsakis added the T-lang label Mar 16, 2016
@nikomatsakis nikomatsakis self-assigned this Mar 16, 2016
@oli-obk
Copy link
Contributor

oli-obk commented Mar 16, 2016

#1215 also addresses the borrowck problem with accessors, but still has the poor performance issue and there wasn't any talk about traits (yet). The suggestion was some kind of explicit disjoint partitions (e.g. struct field names) that can be mentioned in references to allow partial borrows.


```rust
trait Trait {
field1: Type1, // <-- fields within a block separated by commas.
Copy link
Contributor

@ticki ticki Mar 16, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another possibility is to declare "fields" (accessors) using a syntax similar to variable declaration:

trait Trait {
    let field1: Type1;
    let field2: Type2;

    fn foo();
}

This also signifies that it isn't really a field, but more an associated value. It also looks nicer in impls.

Copy link
Contributor

@oli-obk oli-obk Mar 16, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this syntax is mentioned 10 lines below.

Copy link
Contributor

@ticki ticki Mar 16, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, well. I missed that.

Copy link
Contributor Author

@nikomatsakis nikomatsakis Mar 16, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another possibility is to declare "fields" (accessors) using a syntax similar to variable declaration:

trait Trait {
    let field1: Type1;
    let field2: Type2;

    fn foo();
}

This also signifies that it isn't really a field, but more an associated value.

Yeah, I don't hate this. It might be better, all things considered. I
like having all trait items start with a keyword, probably helps us
with future expansions of the syntax, and avoids the awkward , vs
; quesiton.

Copy link
Contributor

@ticki ticki Mar 16, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also has a symmetry to type.

@Ericson2314
Copy link
Contributor

Ericson2314 commented Mar 16, 2016

I like this a lot---it's broadly useful even outside the realm of "virtual structs", and very orthogonal to the listed future work, which are the good smells for this sort of thing.

[Kinda off topic] Another route of generalization is "first class lvalues". I don't know what his would look like, but it might be useful wrt things like map entry API. Teaching the borrow checker that entries for disjoint keys are disjoint would be neat. This is absolutely out of scope for this RFC, but if this is accepted, it opens the door to further exploration in that direction---great!

languages. This means that if, e.g., `Circle` wanted to override the
`highlight` method but also call out to the prior version, it can't
easily do so. Super calls are not strictly needed thoug, as one can
always refactor the super call into a free fn.
Copy link

@futile futile Mar 17, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section contains references to types such as Container and Circle which are not mentioned in the RFC anywhere else. Probably a left-over from previous revisions?

@Manishearth
Copy link
Member

Manishearth commented Mar 17, 2016

Note that as an inheritance system this is not useful for Servo's DOM. This is just a datapoint, not a reason to block it 😄 Otherwise big 👍 , sounds useful.

@petrochenkov
Copy link
Contributor

petrochenkov commented Mar 17, 2016

Any plans to incorporate mutability in this RFC?
E.g. if I want to emulate a getter function with a trait field, then I wouldn't want this field to be mutable.
Maybe these fields should even be immutable by default.

trait Tr {
    x: u8, // Immutable field access, "getter"
    mut y: u8, // Mutable field access, "setter/getter"
}

@eddyb
Copy link
Member

eddyb commented Mar 17, 2016

@petrochenkov That would be great with fields in inherent impls!
It would mean you can have a private field with an immutable public view in an impl, so you can mutate it, but not anyone else.

@jFransham
Copy link

jFransham commented Mar 17, 2016

Big 👍 on this, I personally would prefer that trait fields are not immutable by default, but that mutability is controlled by whether or not the concrete value is mutable. I think if we can work out a general-case disjointness checking solution (the syntax that comes to mind is fn modstigate(self: &Self { field_a }) -> &TypeOfFieldA) then we could keep the semantics of trait fields and struct fields consistent, and use privacy to control field mutability on both (like we do already).

@kylone
Copy link

kylone commented Mar 17, 2016

@jFransham Just to be clear, "use privacy to control field mutability" means using Cell & RefCell in std::cell, right?

@pnkfelix
Copy link
Member

pnkfelix commented Mar 17, 2016

@Manishearth

Note that as an inheritance system this is not useful for Servo's DOM

Just to be clear: would something like the extension described in the Embedded notation and prefix layout section do more to address Servo's use case?

Or do you more broadly mean that we need all the things list in the Other changes section that followed that?

Or do you mean that there is something Servo needs that is not addressed by the items enumerated there?

@Manishearth
Copy link
Member

Manishearth commented Mar 17, 2016

@pnkfelix embedded notation works pretty well.

@golddranks
Copy link

golddranks commented Mar 17, 2016

@jFransham I don't quite understand what you're saying, but traits are basically interfaces. You code against an interface. If you mean by "concrete value" a field in the struct that implements a trait, how could you code generically against the interface, without the trait saying whether the value is mutable or immutable? You can't.

@eddyb
Copy link
Member

eddyb commented Mar 17, 2016

@golddranks The mutability depends on whether the value implementing the trait is in a mutable slot or not. This is already the case, e.g. if you write accessors, you can call setters that take &mut self only if the value is in a mutable slot.

@golddranks
Copy link

golddranks commented Mar 17, 2016

@eddyb Ah, I see. Pardon my ignorance.

@nikomatsakis
Copy link
Contributor Author

nikomatsakis commented Mar 17, 2016

@petrochenkov

Any plans to incorporate mutability in this RFC?

I did not have any such plans. It's an interesting thought. I sort of wish we declared fields as mut as well, in which case this would be consistent. But we don't.

I think I would be more in favor if we also planned to add a mut keyword to ordinary fields and then to lint against assigning directly to a field (or a projection from a field) unless it is declared as mut. Put another way: you would be able to assign to x.f if (a) x is mutable, as today and (b) f is mutable. If (a) is violated, you get an error (as today). If (b) is violated, the code compiles, but you get a lint warning. Obviously the rules would still be mildly different then but if you followed "best practices" it would be consistent.

@nikomatsakis
Copy link
Contributor Author

nikomatsakis commented Mar 17, 2016

@Manishearth

Note that as an inheritance system this is not useful for Servo's DOM. This

I think you and @pnkfelix hashed this out, but to be clear: as the RFC states, this RFC alone is not intended to solve Servo's "DOM problem", but it is a major building block towards doing so. The section on future work lays out the additional steps that I believe would be needed to make a truly ergonomic DOM implementation. If you think anything else is required, it'd be good to speak up. =)

@Manishearth
Copy link
Member

Manishearth commented Mar 17, 2016

Yeah, understood. I personally don't really feel Servo needs a better DOM solution at this stage (If it exists, sure, we'd use it, but I don't want to push for it). I'm happy with our current set of hacks, and the only (minor) improvement I'd like would be some layout-guarantees so that the transmutes aren't technically UB.

I was just pointing out that if the motivation behind this was partially due to use cases like Servo's DOM, it doesn't apply cleanly there.

Basically, Servo would need cheap upcasting. I think with this proposal you need to use trait objects to get that effect, if it is even possible.

@nikomatsakis
Copy link
Contributor Author

nikomatsakis commented Mar 17, 2016

I don't have time to edit the draft now, but I've added a "planned edits" section to the main area.

@kbknapp
Copy link

kbknapp commented Mar 18, 2016

Huge 👍 I've wanted this for a while but there's no way I could have articulated it that well!

@aturon
Copy link
Member

aturon commented Feb 14, 2018

Closing as postponed; activity should happen on the dedicated repo.

@aturon aturon closed this Feb 14, 2018
@kevincox
Copy link

kevincox commented Mar 6, 2018

I created https://internals.rust-lang.org/t/fields-in-traits/6933 to further discuss use cases. I started it off with some broad categories but would be interested in hearing more, and more specific use cases.

@Centril Centril added the postponed label Mar 7, 2018
@eddyb eddyb mentioned this pull request May 25, 2018
@mati865
Copy link

mati865 commented Jun 3, 2019

OK, after some discussion in the @rust-lang/lang meeting, it seemed clear that while we are still interested in a change like this, we don't have the bandwidth to push this through right now, so we're going to postpone the change.

Is it the right time to reconsider this?

@oli-obk
Copy link
Contributor

oli-obk commented Jun 3, 2019

I don't think it really fits into this year's roadmap of "cleanups and finishing things"

@gnzlbg
Copy link
Contributor

gnzlbg commented Jun 3, 2019

IIRC @ubsan and @rpjohnst had an interesting discussion on discord about this problem which is not written down anywhere. Internals might be a good place to continue that.

@tiby312
Copy link

tiby312 commented Jan 19, 2020

I just wanted to say I think this is a great idea. I've been making a 2d graphics library in rust, and I usually have to result to unsafe code when I want to force a trait object function to return a reference to a member of itself.

trait HasPosition{
    fn pos(&self)->&[f32;2];
}

WIth this trait I can't guarentee that the reference returned from pos() is

  1. A reference to a member of the object.
  2. Is always the same offset into said object.
  3. Always returns the same value provided I have a read-only reference to the object.

Using the accessor methods in that you lose all these guarentees.

In order to get these guarentees, I have to make the trait unsafe, and add documentation that the implementor of HasPosition must uphold.

I want these guarentees so that I can figure out the vertex stride when setting the opengl vertex attributes. Not really a thing you do everyday, but still an issue for my unusual use case.

So this issue I'm having isn't a problem with code-reuse, but it is just the fact of losing guarantees when using accessor methods over straight up member access. I think giving the traits members where the implementer can map the trait member to one of their own members is a great idea and doesn't have the multiple inheritance problem.

@sighoya
Copy link

sighoya commented Dec 14, 2020

Any update on this?

@ibraheemdev
Copy link
Contributor

ibraheemdev commented Dec 21, 2020

Is now the right time to reconsider this?

@aniketfuryrocks
Copy link

aniketfuryrocks commented May 3, 2021

any updates on this issue?
This will highly reduce code size for wasm.

@golddranks
Copy link

golddranks commented May 3, 2021

This will highly reduce code size for wasm.

Does it? I'd expect that any getter method that accounts to just returning a field would be trivially optimized away. (That might not be possible with trait objects, in which case you are absolutely correct – did you think of that case specifically?) Do you have some real life code that shows any difference?

I was thinking that the real impact of this idea is because it allows the borrow checker to be more flexible about using non-overlapping fields with traits, not about binary size.

@zackfall
Copy link

zackfall commented Jul 20, 2021

Some update about this?

@dbsxdbsx
Copy link

dbsxdbsx commented Jul 25, 2021

I just wanted to say I think this is a great idea. I've been making a 2d graphics library in rust, and I usually have to result to unsafe code when I want to force a trait object function to return a reference to a member of itself.

trait HasPosition{
    fn pos(&self)->&[f32;2];
}

WIth this trait I can't guarentee that the reference returned from pos() is

  1. A reference to a member of the object.
  2. Is always the same offset into said object.
  3. Always returns the same value provided I have a read-only reference to the object.

Using the accessor methods in that you lose all these guarentees.

In order to get these guarentees, I have to make the trait unsafe, and add documentation that the implementor of HasPosition must uphold.

I want these guarentees so that I can figure out the vertex stride when setting the opengl vertex attributes. Not really a thing you do everyday, but still an issue for my unusual use case.

So this issue I'm having isn't a problem with code-reuse, but it is just the fact of losing guarantees when using accessor methods over straight up member access. I think giving the traits members where the implementer can map the trait member to one of their own members is a great idea and doesn't have the multiple inheritance problem.

I faced similar situation, I just wonder when this trait would be added. Does this feature violate the philosophy of Rust---("Composition offers better test-ability of a class than Inheritance")?

@xinyu391
Copy link

xinyu391 commented Oct 20, 2021

How is it going?

@omarabid
Copy link

omarabid commented Dec 25, 2021

Another bump for this feature. It seems that it has stalled.

@erikpols
Copy link

erikpols commented Jan 9, 2022

Another bump. Would be very useful. Main reason would be to get rid of large amounts of getter methods.

@ic3man5
Copy link

ic3man5 commented Apr 10, 2022

Another bump. +1 from me.

I found this from here:

https://stackoverflow.com/a/48470287/926217

@dbsxdbsx
Copy link

dbsxdbsx commented Apr 11, 2022

Another bump. +1 from me.

I found this from here:

https://stackoverflow.com/a/48470287/926217

But if this feature is done, does it mean that it is a kind of inheritance mechanism? Seems that Rust team don't like inheritance. And for most situation, composition is better than inheritance.

@golddranks
Copy link

golddranks commented Apr 11, 2022

@dbsxdbsx Rust has nothing ideological against inheritance, features are discussed by their merits and costs. But, this feature would not behave the same way as inheritance. (Single) inheritance defines a tree-like structure of canonical relationships between types where those below in the hierarchy always conform to the "interface" defined by those above. However, traits behave like interfaces but they are NOT types, so in Rust, the hierarchy of types is always flat. This feature doesn't change that. This feature is already currently implementable by setters and getters, but the difference is that native support for field mappings would solve some pain points around the borrow checker, as the compiler is then able to reason about the disjointness of the fields.

@dbsxdbsx
Copy link

dbsxdbsx commented Apr 11, 2022

@dbsxdbsx Rust has nothing ideological against inheritance, features are discussed by their merits and costs. But, this feature would not behave the same way as inheritance. (Single) inheritance defines a tree-like structure of canonical relationships between types where those below in the hierarchy always conform to the "interface" defined by those above. However, traits behave like interfaces but they are NOT types, so in Rust, the hierarchy of types is always flat. This feature doesn't change that. This feature is already currently implementable by setters and getters, but the difference is that native support for field mappings would solve some pain points around the borrow checker, as the compiler is then able to reason about the disjointness of the fields.

@golddranks, IMHO, I don't see the word flat means you refer to . From my coding experience, like c++, c# and python (has one or multiple layer hierarchy mechanism), if a field can be defined in trait, it seems to be no difference to hierarchy mechanism in c++, since trait can now also be inherited by another trait in rust. But I do agree that the trait system/concept is more powerful in rust, as it makes generic programming more pointed like using concept in modern c++.

By the way, do you agree on " Composition is always better than inheritance"?

@golddranks
Copy link

golddranks commented Apr 11, 2022

So the difference is in that trait is not usable by itself, it is always "abstract". Unlike with languages with inheritance subtyping you also can't have a value or a variable of the "base type/trait". This is what I mean by flat: there are no subtyping relationships between the trait and the implementing type. The trait would also define only the existence of the fields, but no memory layout for them, so all the implementing types would be free to store the fields in any way they wish, unlike with inheritance, where by the usual implementation, the memory layout of the child types are required to contain the memory layout of the base type as a prefix.

I don't agree on "composition is always better than inheritance". I'd say, "composition is usually better than inheritance", but that discussion is kinda out of scope.

@kevinringier
Copy link

kevinringier commented Apr 28, 2022

One more bump for this feature :)

Also found this from https://stackoverflow.com/a/48470287/926217

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
final-comment-period postponed T-lang
Projects
No open projects
Tracker
Merge proposed
Development

Successfully merging this pull request may close these issues.

None yet