Skip to content

Commit fa02f33

Browse files
authored
Merge pull request #3678 from joshtriplett/final
Trait method impl restrictions (`final` methods)
2 parents 11ca03e + cff2f39 commit fa02f33

File tree

1 file changed

+202
-0
lines changed

1 file changed

+202
-0
lines changed

text/3678-final.md

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
- Feature Name: `final`
2+
- Start Date: 2024-07-20
3+
- RFC PR: [rust-lang/rfcs#3678](https://github.com/rust-lang/rfcs/pull/3678)
4+
- Rust Issue: [rust-lang/rust#131179](https://github.com/rust-lang/rust/issues/131179)
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
Support restricting implementation of individual methods within traits, using
10+
the existing unused `final` keyword.
11+
12+
# Motivation
13+
[motivation]: #motivation
14+
15+
When defining a trait, the trait can provide optional methods with default
16+
implementations, which become available on every implementation of the trait.
17+
However, the implementer of the trait can still provide their own
18+
implementation of such a method. In some cases, the trait does not want to
19+
allow implementations to vary, and instead wants to guarantee that all
20+
implementations of the trait use an identical method implementation. For
21+
instance, this may be an assumption required for correctness.
22+
23+
This RFC allows restricting the implementation of trait methods.
24+
25+
This mechanism also faciliates marker-like traits providing no implementable
26+
methods, such that implementers only choose whether to provide the trait and
27+
never how to implement it; the trait then provides all the method
28+
implementations.
29+
30+
One example of a trait in the standard library benefiting from this:
31+
`Error::type_id`, which has thus far remained unstable because it's unsafe to
32+
override. This RFC would allow stabilizing that method so users can call it,
33+
without permitting reimplementation of it.
34+
35+
Another would be the `Read::read_buf_exact` method. Making this `final` would
36+
allow callers to rely on its implementation to be correct, while keeping the
37+
function safe to call. Without this, callers using `unsafe` code must defend
38+
against the possibility of an incorrect `read_buf_exact` implementation (e.g.
39+
returning `Ok(())` without filling the buffer) to avoid UB.
40+
41+
# Explanation
42+
[explanation]: #explanation
43+
44+
When defining a trait, the definition can annotate methods or associated
45+
functions to restrict whether implementations of the trait can define them. For
46+
instance:
47+
48+
```rust
49+
trait MyTrait: Display {
50+
final fn method(&self) {
51+
println!("MyTrait::method: {self}");
52+
}
53+
}
54+
```
55+
56+
A method or associated function marked as `final` must have a default body.
57+
58+
When implementing a trait, the compiler will emit an error if the
59+
implementation attempts to define any method or associated function marked as
60+
`final`, and will emit a suggestion to delete the implementation.
61+
62+
In every other way, an `final` method or associated function acts identically
63+
to any other method or associated function, and can be invoked accordingly:
64+
65+
```rust
66+
fn takes_mytrait(m: &impl MyTrait) {
67+
m.method();
68+
}
69+
```
70+
71+
Note that in some cases, the compiler might choose to avoid placing a `final`
72+
method in the trait's vtable, if the one-and-only implementation does not
73+
benefit from monomorphization.
74+
75+
Note that removing a `final` restriction is a compatible change. (Removing a
76+
default implementation remains a breaking change.)
77+
78+
# Reference-level explanation
79+
[reference-level-explanation]: #reference-level-explanation
80+
81+
At runtime, a `final fn` behaves exactly the same as a `fn`.
82+
83+
Removing `final` may be a non-breaking change. (If `final` was preventing
84+
implementation to prevent a soundness issue, though, this would require
85+
additional care.)
86+
87+
Adding `final` is a breaking change, unless the trait already did not allow
88+
third-party implementations (such as via a sealed trait).
89+
90+
At compile-time, a method declared as `final fn` in a trait must have a
91+
provided body, and cannot be overridden in any `impl`, even an `impl` in the
92+
same crate or module.
93+
94+
`final fn` cannot be combined with `default fn`.
95+
96+
`final` is only allowed in trait definitions. `final` is not allowed on impls
97+
or their items, non-trait functions, or `extern` blocks.
98+
99+
A `final fn` never prevents a trait from having `dyn`-compatibility; the trait
100+
can remain `dyn`-compatible as long as all non-`final` methods support
101+
`dyn`-compatibility. This also means that a `final fn` can always be called on
102+
a `dyn Trait`, even if the same method as a non-`final` `fn` would not have
103+
been `dyn`-compatible.
104+
105+
# Drawbacks
106+
[drawbacks]: #drawbacks
107+
108+
As with any language feature, this adds more surface area to the language.
109+
110+
# Rationale and alternatives
111+
[rationale-and-alternatives]: #rationale-and-alternatives
112+
113+
Instead of or in addition to this, we could allow inherent `impl` blocks for a
114+
`Trait` (e.g. `impl Trait { ... }` without `for Type`). People today already
115+
occasionally write `impl dyn Trait` blocks, since `dyn Trait` is a type and
116+
supports inherent impl blocks; this change would allow generalizing such blocks
117+
by deleting the `dyn`. This has the potential for conceptual complexity or
118+
confusion for new users, as well as potentially affecting the quality of
119+
diagnostics. (It also used to have a meaning in Rust 2015: the same meaning
120+
`impl dyn Trait` now has.) However, it would provide orthogonality, and an
121+
interesting conceptual model.
122+
123+
Rather than using `final`, we could use the `impl(visibility)` syntax from
124+
[RFC 3323](https://rust-lang.github.io/rfcs/3323-restrictions.html). This would
125+
allow more flexibility (such as overriding a method within the crate but not
126+
outside the crate), and would be consistent with other uses of RFC 3323. On the
127+
other hand, such flexibility would come at the cost of additional complexity.
128+
We can always add such syntax for the more general cases in the future if
129+
needed; see the future possibilities section.
130+
131+
Rather than using `final`, we could use `#[final]`. This
132+
concept is somewhat similar to "final" methods in other languages, and we
133+
already have the `final` keyword reserved so we could use either an attribute
134+
or a keyword.
135+
136+
It's possible to work around the lack of this functionality by placing the
137+
additional methods in an extension trait with a blanket implementation.
138+
However, this is a user-visible API difference: the user must import the
139+
extension trait, and use methods from the extension trait rather than from the
140+
base trait.
141+
142+
# Prior art
143+
[prior-art]: #prior-art
144+
145+
This feature is similar to `final` methods in Java or C++.
146+
147+
It's also similar to `sealed` in C#, where `sealed class` is something from
148+
which you can't derive and a base class can use `sealed` on a method to say
149+
derived classes can't `override` it.
150+
151+
# Unresolved questions
152+
[unresolved-questions]: #unresolved-questions
153+
154+
None yet.
155+
156+
# Future possibilities
157+
[future-possibilities]: #future-possibilities
158+
159+
`final` methods do not need to appear in a trait's vtable. However, *if* a
160+
method is `dyn`-compatible, and if it would benefit from monomorphization, we
161+
could optionally put it in the trait's vtable, perhaps with an explicit option
162+
to do so.
163+
164+
We could allow `final fn` methods on `#[marker]` traits, which are currently
165+
not allowed to have any methods (because they can't allow different
166+
implementations in different `impl`s).
167+
168+
As mentioned in the alternatives section, we could allow inherent `impl` blocks
169+
for a `Trait` (e.g. `impl Trait { ... }` without `for Type`). People today
170+
already occasionally write `impl dyn Trait` blocks, since `dyn Trait` is a type
171+
and supports inherent impl blocks; this change would allow generalizing such
172+
blocks by deleting the `dyn`.
173+
174+
When evaluating possible future syntaxes such as `impl Trait { ... }` blocks,
175+
we should take into account:
176+
- The conceptual model we want to present to users
177+
- Whether we anticipate user confusion due to the former meaning of this syntax
178+
in Rust 2015 (prior to the move from `Trait` to `dyn Trait` to write trait
179+
objects)
180+
- Any effect on diagnostic quality
181+
- Whether an additional syntax adds excessive implementation complexity
182+
- How much we want the benefit of allowing `impl dyn Trait` blocks to be
183+
generalized by deleting the `dyn`
184+
185+
We could add additional flexibility using the restriction mechanism defined in
186+
[RFC 3323](https://rust-lang.github.io/rfcs/3323-restrictions.html), using
187+
syntax like `impl(crate)` to restrict implementation of a method or associated
188+
function outside a crate while allowing implementations within the crate.
189+
(Likewise with `impl(self)` or any other visibility.)
190+
191+
We could theoretically allow `final` restrictions on associated consts and
192+
types, as well. If this is simple to implement, we should implement it for all
193+
items that can appear in a trait simultaneously; if it proves difficult to
194+
implement, we should prioritize methods.
195+
196+
We could support some syntax (e.g. `impl(unsafe)`), to make a method safe to
197+
call, but unsafe to override. This would allow the implementation to be
198+
trusted, so that unsafe code can rely on it rather than defending against
199+
incorrect implementations.
200+
201+
We could integrate this with stability markers, to stabilize calling a method
202+
but keep it unstable to *implement*.

0 commit comments

Comments
 (0)