[Pre-RFC] Final Trait Methods

Some other discussions:

https://rust-lang.zulipchat.com/#narrow/stream/213817-t-lang/topic/RFC.3A.20Restrictions/near/303159975

(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 matches 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 :slightly_frowning_face:)

7 Likes