|
| 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