Pre-RFC: Final Trait Methods
Summary
Allow marking trait methods as final
, making them:
- illegal to override in
impl
blocks - exempt from object safety calculations
- always statically dispatched, even when called on a trait object (because there is only one possible implementation)
Motivation
Some trait methods are "extension" methods, adding additional functionality for users, without placing an additional burden on implementers. Usually, these methods only have one correct implementation, and by enforcing this, we should be allowed to do more things with the trait (such as having a final method with generics on an object safe trait).
Example:
use std::io::Result;
trait Decoder {
fn read_buf(&mut self, buf: &mut [u8]) -> Result<usize>;
final fn read_array<const N: usize>(&mut self) -> Result<[u8; N]> {
let mut arr = [0; N];
self.read_buf(&mut arr)?;
Ok(arr)
}
}
Here, read_array
is an extension method, building on other, lower level methods. Implementers don't need to override it. However, if we want Decoder
to be object safe, we have to compromise by adding where Self: Sized
to read_array
, meaning that we cannot call read_array
on an &mut dyn Decoder
. Marking read_array
final allows the trait to remain object safe.
Reference-Level Explanation
Trait methods can be marked final
:
trait Foo {
final fn bar(&self) {
// ..
}
}
final
trait methods must have a body, and cannot be overridden in impl
blocks.
final
trait methods can have any signature, without affecting the trait's object safety.
trait DynSafe {
final fn qux<T>(&self) {
// ..
}
}
Final trait methods:
- are always statically dispatched
- doesn't need to be part of trait vtables
- can have generics and/or be associated
Additionally, associated functions could be dispatched directly from the trait, without specifying the implementing type.
trait Foo {
final fn bar() -> usize {
42
}
}
fn main() {
// this could be legal, although I haven't found a use case for it yet
println!("{}", Foo::bar());
}
Backwards Compatibility
Adding final
to an existing trait method is a breaking change.
However, we could add some sort of #[deprecated_non_final]
attribute
(akin to deprecated_safe
),
and display a deprecation warning in any impl
blocks that override the method.
Removing final
from a trait method is a breaking change.
(Except possibly in some cases where the trait isn't object safe and the method isn't associated, although I haven't fully considered how this could work)
Prior Art
The only other mention I could find of final trait methods (not that I searched very thoroughly): Don't derive `PartialEq::ne`. by nnethercote · Pull Request #98655 · rust-lang/rust · GitHub.