-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Conversation
#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. |
text/0000-fields-in-traits.md
Outdated
|
||
```rust | ||
trait Trait { | ||
field1: Type1, // <-- fields within a block separated by commas. |
There was a problem hiding this comment.
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 impl
s.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
.
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! |
text/0000-fields-in-traits.md
Outdated
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. |
There was a problem hiding this comment.
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?
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. |
Any plans to incorporate mutability in this RFC?
|
@petrochenkov That would be great with fields in inherent |
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 |
@jFransham Just to be clear, "use privacy to control field mutability" means using Cell & RefCell in std::cell, right? |
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? |
@pnkfelix embedded notation works pretty well. |
@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. |
@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 |
@eddyb Ah, I see. Pardon my ignorance. |
I did not have any such plans. It's an interesting thought. I sort of wish we declared fields as I think I would be more in favor if we also planned to add a |
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. =) |
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. |
I don't have time to edit the draft now, but I've added a "planned edits" section to the main area. |
Huge 👍 I've wanted this for a while but there's no way I could have articulated it that well! |
any updates on this issue? |
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. |
Some update about this? |
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")? |
How is it going? |
Another bump for this feature. It seems that it has stalled. |
Another bump. Would be very useful. Main reason would be to get rid of large amounts of getter methods. |
Another bump. +1 from me. I found this from here: |
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. |
@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 By the way, do you agree on " Composition is always better than inheritance"? |
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. |
One more bump for this feature :) Also found this from https://stackoverflow.com/a/48470287/926217 |
Bumping for this too, just came across this while learning the lang :) |
This would greatly improve code readability. Would love to see this implemented. Using getter methods just feels like a hacky workaround for something which should (in my opinion be possible directly. The main use case I can think of is using a property in the default implementation of a trait's function. |
Would love this feature. Would greatly improve readability in many cases, instead of having to do so many getter/setter methods just to access some fields. I feel getter/setter methods have their own place and a reason (if there's more complicated workings going on there, or if you need to hide implementation details), but sometimes all you need/want is simple field access Reading the comments here, it seems there might be a lot to change to support such a thing. |
@nikomatsakis, may I ask if this feature is totally banned or just postponed? |
Another bump, any update for this? |
I found here reading why-im-dropping-rust. Looks like Rust is hitting the same issues with C++. I remember people promoting named-parameter-idiom in C++ way back then, to either delay or reject designated initializers... I always wished I could use designated initializers and most of the time I had to move to C because... (long list of reasons), It looks like either Rust language designers really love getter methods in a similar way the C++ committee loved named-parameter-idioms and/or there is a core design philosophy is really contradicting this proposal; If either is true, then even if you get this thing let's say 2033, it will be a similar story. Also don't buy, too complex or backward compatibility, this is not a workload issue, this is a decision and direction. |
Actually, the primary point of this is to change how the lifetime system works by exposing struct splitting at the language level (in effect), and is significant work because rewriting the lifetime system to handle it and many other things is taking ongoing experimental work. Others can provide more details; I'm more divorced from the compiler's ongoings than I used to be. It's not as simple as "this abstracts over a pointer", and the primary point isn't that getters are annoying, it's that this lets the compiler understand when two references don't overlap. I think this is valuable too, though, don't get me wrong. Just not as easy as it at first appears. If it was as simple as getters vs. not-getters you could solve it with macros or various hacks like what readonly does under the hood. |
Would allowing fields in trait make the trait be like Java abstract class? Is Rusting on the route to have inheritance? Can we just use macros to generate the accessor methods in implementing types? |
same question/doubt here. |
The comment below is kind of proof that it is intended this way. The Rust community wants to avoid the mistakes of C++, but they have a similar mindset. This is about having a feature like concepts even C++ has now: https://stackoverflow.com/questions/60434579/using-concepts-for-checking-if-a-type-t-has-a-field-f Example code they have looks like below: void doSomething(auto i) requires requires { i.foo; } { /* */ } If you ask me this looks much more like generic programming than Java-like inheritance. But I guess high probably it is right, Rust should never have this feature, or it may be a waste of effort, or it smells like Java. |
For those who just want to avoid redundant code of a FIXED but mutable struct field, you can work around with this: trait MyTrait {
fn get_field_a(&self) -> bool;
}
macro_rules! add_my_trait_impl {
($struct_name:ident { $($field_name:ident : $field_type:ty),* }) => {
#[derive(Debug)]
struct $struct_name {
$($field_name : $field_type,)*
a: bool,
}
impl MyTrait for $struct_name {
fn get_field_a(&self) -> bool {
self.a
}
}
}
}
add_my_trait_impl! {MyStruct2 {}}
add_my_trait_impl!(MyStruct1 { x: i32, y: i32 });
fn main() {
let my_struct1 = MyStruct1 {
x: 1,
y: 2,
a: true,
};
let my_struct2 = MyStruct2 { a: false };
println!("{:?}", my_struct1.get_field_a());
println!("{:?}", my_struct2.get_field_a());
} In this way, you don't have to write redundant code for |
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 }
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(decided against this)let
syntax for declaring fields in traits and impls.mut
is required for mutable access. (At minimum, add as an unresolved question.)