-
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
Trait method impl restrictions #3678
base: master
Are you sure you want to change the base?
Conversation
We've past proposals for inherent trait methods I think, mostly proposing a second inherent looking There is however a question of default speed vs size optimizations in vtables, aka do these methods appear in the vtable, or do they have a generic implementation for every type? Also, how does one flag that these method should appear in a vtable, or should use a generic implementation for every type? |
It might be better to have this as an attribute rather than yet another potential keyword position for parsing to deal with. Otherwise, great. |
@Lokathor wrote:
I get that, but on the flip side, that'd be inconsistent with RFC 3323, and it seems awkward to use a keyword in one place and an attribute in another. |
@burdges wrote:
That might be equivalent to a subset of this, depending on the details. I haven't seen those proposals.
In theory, if you have a method that can't be overridden outside of the crate/module, and nothing overrides it, you could optimize by omitting it from the vtable. I don't think that optimization should be mandatory, or required for initial implementation, though. |
I'm more asking what's the best default. If the best default were to be in the vtable, then you can just do
I'd expect If otoh the best default were not to be in the vtable, then rustc should do something more complex. Anyways yeah maybe not relevant here. |
Another future possibility could be to add |
@bluebear94 Interesting! So, rather than requiring |
this would be quite useful for stabilizing |
@programmerjake That's a great use case, thank you! |
This seems like a reasonable and desirable extension to RFC 3323. So I propose... @rfcbot fcp merge Thanks @joshtriplett for writing this up. I note that syntax was left as an open item on RFC 3323, and so we're implicitly carrying that open item here also. |
Team member @traviscross has proposed to merge this. The next step is review by the rest of the tagged team members: Concerns:
Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! cc @rust-lang/lang-advisors: FCP proposed for lang, please feel free to register concerns. |
Big +1 to having something like this. @rfcbot concern visiblity-style-vs-final-style I think these are far more than a syntax difference, so I want to talk about it more. As a couple of examples:
So is the visibility phrasing here actually important? Do we actually need it? The cases I know of don't need it
The provided implementation of a Thus my inclination is that we should do the |
I think |
If it is to be an attribute then I would prefer something like I assume the syntax was chosen for parity with RFC3323, but I think it is okay to deviate from this if it comes with a simplification. |
Note that the RFC allows for
This is something I specifically checked to ensure was there before proposing FCP merge, in part for the reasons you mention. |
This comment was marked as duplicate.
This comment was marked as duplicate.
I think that that just pushes me even more to skip the If we need a special syntax, let's make it (And, aesthetically, |
Avoid implying that this should always happen if possible. The compiler may not always want to do this.
Suggested-by: Tyler Mandry <[email protected]>
@scottmcm I believe I've now addressed your concern. |
@rfcbot resolve reference-text |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
Something I realized: @joshtriplett it's worth mentioning, I think, that we could allow marker traits to have final methods (but not other methods). |
Maybe a little late, but I'd like to suggest another syntax: trait MyTrait: Display {
// stuff
}
impl MyTrait {
fn method(&self) {
println!("MyTrait::method: {self}");
}
} This syntax would basically be an inherent impl block, for traits. It would have the same properties as |
Interesting.
Other arguments:
Personally, I don't care very much — I'm looking forward to being able to use |
Another idea of a use for trait From<T> {
final type Source = T;
fn from(x: Self::Source) -> Self;
} Because whenever I have to repeat a very-long type in the (Not to say that we'd necessarily actually do that in |
@PoignardAzur I do think that's interesting. It makes me think of how it'd be really nice to split out provided methods into various partial implementations that might differ for different trait bounds -- like having a provided That said, I actually like having the word "final" in there, whether as I wonder if that syntax should instead be sugar for an unnamed extension trait... |
You could add the keyword to those impl blocks. #[final]
impl MyTrait {
fn method(&self) {
println!("MyTrait::method: {self}");
}
}
// or
final impl MyTrait {
fn method(&self) {
println!("MyTrait::method: {self}");
}
}
I mean, an unnamed extension trait implemented on all instances of the trait is basically indistinguishable from an impl block with final items, right? |
They'd just be inherent methods (on traits). |
I have mixed feelings towards the newly proposed syntax, i.e. "just think it like inherent methods on types!" vs "all of the trait's contract should be visible from the trait definition itself!" :( (For the latter, a final method, though cannot be overrided, is also a part of the trait's "contract" just like all other ordinary methods.) |
Regarding contracts, here is a hypothetical future feature that would build on
In this lens, the trait can end up having two different API surfaces: one for callers and one for implementors. When I started writing, I thought this was a good argument for |
If you wanted to restrict calling a trait method to the containing module, then you could make it take an instance of a type that can only be constructed in the module: pub struct AToken(());
pub trait A {
fn called_privately(&self, a: u32, b: &str, token: AToken) -> bool;
}
// somewhere later in self:
fn do_something(a: &impl A) {
let _ = a.called_privately(5, "test", AToken(()));
} |
|
@kpreid raises what I also think is a key point here, which is that traits define both the implementable interface and part of the callable one. In my view, in Rust, defining the implementable interface is their unique role, because we have other ways to define callable interfaces. E.g., this of course works today: trait Tr {
// Nothing in the implementable interface...
}
impl dyn Tr + '_ {
// ...but there's now something in the callable one when used
// with `dyn`.
fn f(&self) {}
}
fn g(x: &dyn Tr) {
x.f();
} If we could replace the above with: trait Tr {
// There's still nothing in the implementable interface.
}
impl Tr { // <--- Think of this like `impl (impl Tr) { .. }`.
// There's now something in the callable interface.
fn f(&self) {}
}
fn g(x: &dyn Tr) {
x.f();
} ...such that it would also now allow: // Hey, look at that, increased parity between
// `dyn Trait` and `impl Trait`.
fn h(x: &impl Tr) {
x.f();
} That would make a lot of sense to me. We made bare trait syntax into a hard error in Rust 2021 to recover that syntactic space in the language. Maybe it will have been for this. (Or maybe we work out some other syntax that achieves the key idea here.) (There are interesting questions too about how to notate whether such methods should go in the vtable, as sometimes that is what is wanted and something it is not. I could see an argument that perhaps the trait's necessary role isn't necessarily to define either the implementable interface or the callable one, but instead to define the vtable contents. That'd be a coherent position, but it seems maybe too oriented around optimization rather than language semantics (especially as traits are often used statically without a vtable at all). Probably an attribute seems more natural to me for controlling this.) |
(Aside, TC: it'd be nice to have your concerns in the thread here, rather than in an external document.) When I look at
I prefer to think about the intent someone has, rather than any particular language details. (Niko's https://internals.rust-lang.org/t/bikeshed-rename-catch-blocks-to-fallible-blocks/7121/4?u=scottmcm post really changed how I think about a bunch of these things, where it's not quite the same as something else but it's close enough that reusing existing intuition is valuable.) If I'm using a Whether that's (Whether that's |
This is where I point out what we're going to decide in... ...which is that this program will work and print "cat": trait Mammal {
//#[final] // <--- Write this to prevent the "override" below?
fn frob(&self) { println!("mammal") }
}
trait Cat: Mammal { // "Cat extends Mammal..."
fn frob(&self) { println!("cat") }
}
fn f(x: impl Cat) {
x.frob() // Prints "cat".
} That is, there already is this way of "extending" traits with subtraits, and there will soon be this other axis for the "overriding" of a method. This other axis is a better fit for And given that we already support the conceptually similar: trait Tr {}
impl dyn Tr + '_ {
// This is an inherent method on
// `dyn` instances of the trait.
fn f(&self) {}
}
fn g(x: &dyn Tr) {
x.f();
} ...what we're talking about in this RFC seems to me much more like "inherent" methods than "final" ones, whatever syntax we choose for that. To your point about considering intent, my intent here would be to add an inherent method, carrying over my intuitions about what that means elsewhere in Rust, such as in the above. That is, I don't think we need to import a concept here. We already have one! @joshtriplett and I talked this through last week, and on the basis of these considerations, he's planning to update this RFC to change With that, I'll be happy to check the box here (or propose FCP merge myself), and we can dig into further discussion on the open item when time permits. |
Support restricting implementation of individual methods within traits, using the already reserved
final
keyword.This makes it possible to define a trait that any crate can implement, but disallow overriding one of the trait's methods or associated functions.
This was inspired in the course of writing another RFC defining a trait, which wanted precisely this feature of restricting overrides of the trait's method. I separated out this feature as its own RFC, since it's independently useful for various other purposes, and since it should be available to any crate and not just the standard library.
Rendered
Tracking: