|
| 1 | +- Feature Name: safe_unsafe_trait_methods |
| 2 | +- Start Date: 2018-01-30 |
| 3 | +- RFC PR: [rust-lang/rfcs#2316](https://github.com/rust-lang/rfcs/pull/2316) |
| 4 | +- Rust Issue: [rust-lang/rust#87919](https://github.com/rust-lang/rust/issues/87919) |
| 5 | + |
| 6 | +# Summary |
| 7 | +[summary]: #summary |
| 8 | + |
| 9 | +This RFC allows safe implementations of `unsafe` trait methods. |
| 10 | +An `impl` may implement a trait with methods marked as `unsafe` without |
| 11 | +marking the methods in the `impl` as `unsafe`. This is referred to as |
| 12 | +*overconstraining* the method in the `impl`. When the trait's `unsafe` |
| 13 | +method is called on a specific type where the method is known to be safe, |
| 14 | +that call does not require an `unsafe` block. |
| 15 | + |
| 16 | +# Motivation |
| 17 | +[motivation]: #motivation |
| 18 | + |
| 19 | +A trait which includes unsafe methods in its definition permits its impls to |
| 20 | +define methods as unsafe. Safe methods may use `unsafe { .. }` blocks inside |
| 21 | +them and so both safe and `unsafe` methods may use unsafe code internally. |
| 22 | + |
| 23 | +The key difference between safe and unsafe methods is the same as that |
| 24 | +between safe and unsafe functions. Namely, that calling a safe method with |
| 25 | +inputs and state produced by other safe methods never leads to memory |
| 26 | +unsafety, while calling a method marked as `unsafe` may lead to such unsafety. |
| 27 | +As such, it is up to the caller of the `unsafe` method to fulfill a set of |
| 28 | +invariants as defined by the trait's documentation (the contract). |
| 29 | + |
| 30 | +The safe parts of Rust constitute a language which is a subset of unsafe Rust. |
| 31 | +As such, it is always permissible to use the safe subset within unsafe contexts. |
| 32 | +This is currently however not fully recognized by the language as `unsafe` trait |
| 33 | +methods must be marked as `unsafe` in `impl`s even if the method bodies in such |
| 34 | +an `impl` uses no unsafe code. This is can currently be overcome by defining a |
| 35 | +safe free function or inherent method somewhere else and then simply delegate |
| 36 | +to that function or method. Such a solution, however, has two problems. |
| 37 | + |
| 38 | +## 1. Needless complexity and poor ergonomics. |
| 39 | + |
| 40 | +When an `unsafe` method doesn't rely on any unsafe invariants, it still |
| 41 | +must be marked `unsafe`. Marking methods as `unsafe` increases the amount of |
| 42 | +scrutiny necessary during code-review. Extra care must be given to ensure that |
| 43 | +uses of the function are correct. Additionally, usage of `unsafe` functions |
| 44 | +inside an `unsafe` method does not require an `unsafe` block, so the method |
| 45 | +implementation itself requires extra scrutiny. |
| 46 | + |
| 47 | +One way to avoid this is to break out the internals of the method into a |
| 48 | +separate safe function. Creating a separate function which is only used |
| 49 | +at a single place is cumbersome, and does not encourage the keeping of |
| 50 | +`unsafe` to a minimum. The edit distance is also somewhat increased. |
| 51 | + |
| 52 | +## 2. `unsafe` method `impl`s might not require any `unsafe` invariants |
| 53 | + |
| 54 | +The implemented trait method for that specific type, which you know only has |
| 55 | +a safe implementation and does not really need `unsafe`, can't be used in a |
| 56 | +safe context. This invites the use of an `unsafe { .. }` block in that context, |
| 57 | +which is unfortunate since the compiler could know that the method is really |
| 58 | +safe for that specific type. |
| 59 | + |
| 60 | +## In summation |
| 61 | + |
| 62 | +The changes proposed in this RFC are intended to increase ergonomics and |
| 63 | +encourage keeping `unsafe` to a minimum. By doing so, a small push in favor |
| 64 | +of correctness is made. |
| 65 | + |
| 66 | +# Guide-level explanation |
| 67 | +[guide-level-explanation]: #guide-level-explanation |
| 68 | + |
| 69 | +Concretely, this RFC will permit scenarios like the following: |
| 70 | + |
| 71 | +## *Overconstraining* |
| 72 | + |
| 73 | +First consider a trait with one or more unsafe methods. |
| 74 | +For simplicity, we consider the case with one method as in: |
| 75 | + |
| 76 | +```rust |
| 77 | +trait Foo { |
| 78 | + unsafe fn foo_computation(&self) -> u8; |
| 79 | +} |
| 80 | +``` |
| 81 | + |
| 82 | +You now define a type: |
| 83 | + |
| 84 | +```rust |
| 85 | +struct Bar; |
| 86 | +``` |
| 87 | + |
| 88 | +and you implement `Foo` for `Bar` like so: |
| 89 | + |
| 90 | +```rust |
| 91 | +impl Foo for Bar { |
| 92 | + // unsafe <-- Not necessary anymore. |
| 93 | + fn foo_computation(&self) -> u8 { 0 } |
| 94 | +} |
| 95 | +``` |
| 96 | + |
| 97 | +Before this RFC, you would get the following error message: |
| 98 | + |
| 99 | +``` |
| 100 | +error[E0053]: method `foo_computation` has an incompatible type for trait |
| 101 | + --> src/main.rs:11:5 |
| 102 | + | |
| 103 | +4 | unsafe fn foo_computation(&self) -> u8; |
| 104 | + | --------------------------------------- type in trait |
| 105 | +... |
| 106 | +11 | fn foo_computation(&self) -> u8 { 0 } |
| 107 | + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected unsafe fn, found normal fn |
| 108 | + | |
| 109 | + = note: expected type `unsafe fn(&Bar) -> u8` |
| 110 | + found type `fn(&Bar) -> u8` |
| 111 | +``` |
| 112 | + |
| 113 | +But with this RFC implemented, you will no longer get an error in this case. |
| 114 | + |
| 115 | +This general approach of giving up (restricting) capabilities that a trait |
| 116 | +provides to you, such as the ability to rely on caller-upheld invariants |
| 117 | +for memory safety, is known as *overconstraining*. |
| 118 | + |
| 119 | +## Taking advantage of *overconstraining* |
| 120 | + |
| 121 | +You now want to use `.foo_computation()` for `Bar`, and proceed to do so as in: |
| 122 | + |
| 123 | +```rust |
| 124 | +fn main() { |
| 125 | + // unsafe { <-- no unsafe needed! |
| 126 | + |
| 127 | + let bar = Bar; |
| 128 | + let val = bar.foo_computation(); |
| 129 | + |
| 130 | + // other stuff.. |
| 131 | + |
| 132 | + // } |
| 133 | +} |
| 134 | +``` |
| 135 | + |
| 136 | +This is permitted since although `foo_computation` is an `unsafe` method as |
| 137 | +specified by `Foo`, the compiler knows that for the specific concrete type `Bar`, |
| 138 | +it is defined as being safe, and may thus be called within a safe context. |
| 139 | + |
| 140 | +## Regarding API stability and breaking changes |
| 141 | + |
| 142 | +Note however, that the ability to call *overconstrained* methods with |
| 143 | +the absence of `unsafe` in a safe context means that introducing `unsafe` |
| 144 | +later is a breaking change if the type is part of a public API. |
| 145 | + |
| 146 | +## Impls for generic types |
| 147 | + |
| 148 | +Consider the type `Result<T, E>` in the standard library defined as: |
| 149 | + |
| 150 | +```rust |
| 151 | +pub enum Result<T, E> { |
| 152 | + Ok(T), |
| 153 | + Err(E), |
| 154 | +} |
| 155 | +``` |
| 156 | + |
| 157 | +Let's now implement `Foo` for `Result<T, E>` without using `unsafe`: |
| 158 | + |
| 159 | +```rust |
| 160 | +impl<T, E> Foo for Result<T, E> { |
| 161 | + fn foo_computation(&self) -> u8 { |
| 162 | + // Let's assume the implementation does something interesting.. |
| 163 | + match *self { |
| 164 | + Ok(_) => 0, |
| 165 | + Err(_) => 1, |
| 166 | + } |
| 167 | + } |
| 168 | +} |
| 169 | +``` |
| 170 | + |
| 171 | +Since `Result<T, E>` did not use `unsafe` in its implementation of `Foo`, you |
| 172 | +can still use `my_result.foo_computation()` in a safe context as shown above. |
| 173 | + |
| 174 | +## Recommendations |
| 175 | + |
| 176 | +If you do not plan on introducing `unsafe` for a trait implementation of |
| 177 | +your specific type that is part of a public API, you should avoid marking |
| 178 | +the `fn` as `unsafe`. If the type is internal to your crate, you should |
| 179 | +henceforth never mark it as `unsafe` unless you need to. If your needs |
| 180 | +change later, you can always mark impls for internal types as `unsafe` then. |
| 181 | + |
| 182 | +Tools such as `clippy` should preferably lint for use of `unsafe`, |
| 183 | +where it is not needed, to promote the reduction of needless `unsafe`. |
| 184 | + |
| 185 | +# Reference-level explanation |
| 186 | +[reference-level-explanation]: #reference-level-explanation |
| 187 | + |
| 188 | +Assuming a `trait` which defines some `fn`s marked as `unsafe`, an `impl` |
| 189 | +of that trait for a given type may elect to not mark those `fn`s as `unsafe` |
| 190 | +in which case the bodies of those `fn`s in that `impl` are type checked as |
| 191 | +safe and not as `unsafe`. A Rust compiler will keep track of whether the |
| 192 | +methods were implemented as safe or `unsafe`. |
| 193 | + |
| 194 | +When a trait method is called for a type in a safe context, the type checker |
| 195 | +will resolve the `impl` for a specific known and concrete type. If the `impl` |
| 196 | +that was resolved implemented the called method without an `unsafe` marker, |
| 197 | +the compiler will permit the call. Otherwise, the compiler will emit an error |
| 198 | +since it can't guarantee that the implementation was marked as safe. |
| 199 | + |
| 200 | +With respect to a trait bound on a type parameter `T: Trait` for a trait with |
| 201 | +unsafe methods, calling any method of `Trait` marked as `unsafe` for `T` is |
| 202 | +only permitted within an `unsafe` context such as an `unsafe fn` or within an |
| 203 | +`unsafe { .. }` block. |
| 204 | + |
| 205 | +# Drawbacks |
| 206 | +[drawbacks]: #drawbacks |
| 207 | + |
| 208 | +While this introduces no additional syntax, it makes the rule-set of the |
| 209 | +language a bit more complex for both the compiler and the for users of the |
| 210 | +language. The largest additional complexity is probably for the compiler |
| 211 | +in this case, as additional state needs to be kept to check if the method |
| 212 | +was marked as safe or `unsafe` for an `impl`. |
| 213 | + |
| 214 | +# Rationale and alternatives |
| 215 | +[alternatives]: #alternatives |
| 216 | + |
| 217 | +[RFC 2237]: https://github.com/rust-lang/rfcs/pull/2237 |
| 218 | + |
| 219 | +This RFC was designed with the goal of keeping the language compatible |
| 220 | +with potential future effects-polymorphism features. In particular, the |
| 221 | +discussion and design of [RFC 2237] was considered. No issues were found |
| 222 | +with respect to that RFC. |
| 223 | + |
| 224 | +No other alternatives have been considered. There is always the obvious |
| 225 | +alternative of not implementing the changes proposed in any RFC. For this RFC, |
| 226 | +the impact of not accepting it would be too keep the problems as explained |
| 227 | +in the [motivation] around. |
| 228 | + |
| 229 | +# Unresolved questions |
| 230 | +[unresolved]: #unresolved-questions |
| 231 | + |
| 232 | +There are currently no unresolved questions. |
0 commit comments