RFC 3678, "Trait method impl restrictions (
finalmethods)", was accepted in January 2026 and is implemented on nightly behind#![feature(final_associated_functions)]. What people are fighting about now is twofold: whether forbidding overrides is the right call at all, and how afinal fnshould behave when called throughdyn. The FCP on whether the standard library may ship stable APIs that usefinal fnis still open.
final rule out downstream use cases that library authors never anticipated?" The author cites painful experiences with Java's final and proposes a lint instead of a ban: warn by default, and let users override explicitly with #[expect(final_method_override, reason = ...)]. Josh Triplett (the RFC author), SkiFire13, ais523 and others pushed back within hours, and the discussion widened into "how far is a no-override rule justified as API design?"final fn, is in a lang/types FCP. It has been stuck since 2026-09-22, when jackh726 filed a concern that there is no document saying what types is actually committing to. The concrete PR waiting on it is rust#160365, which turns Rng::fill_bytes into a final fn (S-blocked).final fn and vtables", scottmcm left open whether a final fn should live in the vtable or be statically dispatched. It is the one unresolved question still listed on the tracking issue, rust#131179.A Rust trait serves two audiences: callers and implementors. A provided method (a method with a default body) serves both with a single declaration.
trait Read {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize>;
// provided: callable, and implementors can override it
fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { /* loop over read */ }
}
When you want something callable whose body nobody can swap out, every option so far has been awkward:
unsafe code assumes the provided implementation is correct, one bad override is UB. The RFC's example is Read::read_buf_exact: unsafe callers must defend against an implementation that returns Ok(()) without filling the buffer.unsafe trait. Every implementor then carries a safety contract for the sake of one method.trait ReadExt: Read { fn foo(&self) {..} } impl<T: Read + ?Sized> ReadExt for T {}. This blocks overrides, but users must import a second trait, so the API splits in two.core::slice::range, which lives outside the trait because overriding it on a trait could be unsound. You lose method syntax.impl dyn Trait { .. }. You can write inherent methods on the dyn type, but they can't be called through impl Trait or generics.The other practical motivation is Error::type_id. Being able to override it would break the soundness of downcast, so it has stayed unstable for years, blocked with private-type workarounds (rust#60784).
#![feature(final_associated_functions)]
trait MyTrait: Display {
final fn method(&self) {
println!("MyTrait::method: {self}");
}
}