Often traits contain extension methods which are not intended to be overwritten. Overriding their implementation may result in correctness bugs. Additionally, accounting for potential overrides may complicate API evolution.
I think something like #[non_overridable] could be a useful addition to the language:
Such methods could also be excluded from vtables, thus potentially reducing their size (i.e. implementation of the extension methods can be reused across trait implementors).
We can achieve a similar result with extension traits, but I think the attribute would be a cleaner and more ergonomic solution.
Good to see that there is already an RFC for that!
Though it would be nice to mention whether dyn-compatible final methods will be or not part of generated vtables since it has implications for optimizing performance vs binary size. (I haven't read the RFC discussion yet)
A final fn never prevents a trait from having dyn-compatibility; the trait
can remain dyn-compatible as long as all non-final methods support
dyn-compatibility. This also means that a final fn can always be called on
a dyn Trait, even if the same method as a non-finalfn would not have
been dyn-compatible.
"Always"? Surely not for Self: Sized methods... but I could see this working to use final on generic methods that would have had a "fake" Self: Sized requirement before just to release it from dyn-compatiblity. Being generic also implies that they're not accessed through the vtable.
I had that thought too, but either the final fn is also declared Self: Sized, meaning it’s not available on dyn for that reason, or it’s not, meaning it can’t call the non-dyn-compatible requirements anyway.
Surely there are other benefits, besides minor sugar, for a new feature?
Personally I can't think of any. Any trait bound and function call which is used by a final method can just as well be replicated on a free function. Final methods also are explicitly exempt from the normal dynamic dispatch and implementation override rules that normal trait methods have. Any trait method which calls a final method can just as well call a corresponding free function.
One potential benefit could be forbidding further specialization. E.g. a specialized trait implementation could declare some method definition as final, forbidding further specialization. But specialization itself is very unstable, and it's unclear whether such a feature could be compatible with it (normally, specialization is allowed/disallowed at the level of type & trait signatures and bounds, and the definitions of methods don't play a part in it).
It would be a generic method that's generic on Self: Trait. It's just that the generic implementation would be common to all types that implement it so that no additional viable entry would be needed.
To be explicit about something that took me a little bit to figure out: this will need to use the vtable, but it will have the same code for any implementer (assuming no inlining or other optimizations that interfere with it) such that it doesn't need its own entry in the same way a fn foo(foo: &impl Trait) doesn't need a vtable entry.
How's that supposed to work? You still need monomorphised instances, because types have different sizes, different ABI conventions based on that. A single function can't have one prologue that can handle every possible argument type.
Here, baz can call Foo methods, and since there's exactly one code implementation of baz, Foo methods can call that baz implementation. And monomorphization can do its magic to make that work for the given type. This should also work fine for trait objects due to the ?Sized on baz.
The only advantage I can see for defining it as a final trait method is that when you import trait Foo somewhere, you also import the baz method rather than having to import it separately. But functionally, I don't see a difference, unless I've misunderstood something. I'd appreciate any corrections.
The .baz() method prints () because it's being monomorphized for () and inserted into the vtable, while the baz function call prints dyn playground::Foo because it's being monomorphized for the trait object type.