Some other discussions:
(As you might guess, I'm a fan of the idea.)
"Prior Art" isn't just Rust art, so you probably want to link to final methods in Java too. (And sealed
in C#, but Rust uses that word to mean something else.)
Remember as you finalize this to follow the https://github.com/rust-lang/rfcs/blob/master/0000-template.md -- you're missing rationale and alternatives right now, which is one of the most important sections. I'd suggest adding a subsection to it for each thing that someone could ask "why did you pick ______?" about, like I'm going to below.
Extra motivation: this allows unsafe
code to trust them as much as a normal function.
For example, range in std::slice - Rust was originally on RangeBounds
, but that means that unsafe code couldn't trust it, so it needed to become an ordinary generic function instead of a trait method to be able to trust the implementation.
Extra motivation: Having #[final]
methods would be reasonable on #[marker]
traits, which currently cannot have methods because with multiple implementations there'd be no way to know which to call.
For example, you could imagine something like
#[marker]
unsafe trait SafeToInitFromZeros: Sized {
#[final]
fn zeroed() -> Self { unsafe { std::mem::zeroed() } }
}
This one isn't necessarily obvious to me.
Suppose we made RangeBounds
object-safe and put slice::range
on it again, as a #[final]
method.
range
is somewhat complicated, because it needs to handle Bound::{Included, Excluded, Unbounded}
on both ends:
If it's not vtable dispatched, then those start_bound
and end_bound
calls will be vtable dispatched, and the match
es will never optimize down, the out-of-bounds checks cannot optimize away, etc. And thus &.. as &dyn RangeBounds<usize>
would be horrible.
Whereas if the final
method monomorphized for the specific impl
, and ended up in the vtable, then there's just the one indirect call to the optimized function, and the inner calls can be static. And thus range
on &.. as &dyn RangeBounds<usize>
would just have the one dyn
call, to something that would directly return 0..bounds.end
without needing to do all the other checks.
Thus my instinct here is to say that, as much as possible, a #[final]
method behaves just like any other method, with the restriction on overrides being the only difference.
After all, if I wish a not-in-the-vtable not-overridable helper for a trait object today, I can define it via impl dyn MyTrait + '_
, without needing final fn
.
The obvious bikeshed: final fn
or #[final] fn
.
(I don't know whether lang has good guidelines on this )