|
| 1 | +- Feature Name: N/A |
| 2 | +- Start Date: 2015-06-4 |
| 3 | +- RFC PR: (leave this empty) |
| 4 | +- Rust Issue: (leave this empty) |
| 5 | + |
| 6 | +# Summary |
| 7 | + |
| 8 | +Adjust the object default bound algorithm for cases like `&'x |
| 9 | +Box<Trait>` and `&'x Arc<Trait>`. The existing algorithm would default |
| 10 | +to `&'x Box<Trait+'x>`. The proposed change is to default to `&'x |
| 11 | +Box<Trait+'static>`. |
| 12 | + |
| 13 | +Note: This is a **BREAKING CHANGE**. The change has |
| 14 | +[been implemented][branch] and its impact has been evaluated. It was |
| 15 | +[found][crater] to cause **no root regressions** on `crates.io`. |
| 16 | +Nonetheless, to minimize impact, this RFC proposes phasing in the |
| 17 | +change as follows: |
| 18 | + |
| 19 | +- In Rust 1.2, a warning will be issued for code which will break when the |
| 20 | + defaults are changed. This warning can be disabled by using explicit |
| 21 | + bounds. The warning will only be issued when explicit bounds would be required |
| 22 | + in the future anyway. |
| 23 | +- In Rust 1.3, the change will be made permanent. Any code that has |
| 24 | + not been updated by that time will break. |
| 25 | + |
| 26 | +# Motivation |
| 27 | + |
| 28 | +When we instituted default object bounds, [RFC 599] specified that |
| 29 | +`&'x Box<Trait>` (and `&'x mut Box<Trait>`) should expand to `&'x |
| 30 | +Box<Trait+'x>` (and `&'x mut Box<Trait+'x>`). This is in contrast to a |
| 31 | +`Box` type that appears outside of a reference (e.g., `Box<Trait>`), |
| 32 | +which defaults to using `'static` (`Box<Trait+'static>`). This |
| 33 | +decision was made because it meant that a function written like so |
| 34 | +would accept the broadest set of possible objects: |
| 35 | + |
| 36 | +```rust |
| 37 | +fn foo(x: &Box<Trait>) { |
| 38 | +} |
| 39 | +``` |
| 40 | + |
| 41 | +In particular, under the current defaults, `foo` can be supplied an |
| 42 | +object which references borrowed data. Given that `foo` is taking the |
| 43 | +argument by reference, it seemed like a good rule. Experience has |
| 44 | +shown otherwise (see below for some of the problems encountered). |
| 45 | + |
| 46 | +This RFC proposes changing the default object bound rules so that the |
| 47 | +default is drawn from the innermost type that encloses the trait |
| 48 | +object. If there is no such type, the default is `'static`. The type |
| 49 | +is a reference (e.g., `&'r Trait`), then the default is the lifetime |
| 50 | +`'r` of that reference. Otherwise, the type must in practice be some |
| 51 | +user-declared type, and the default is derived from the declaration: |
| 52 | +if the type declares a lifetime bound, then this lifetime bound is |
| 53 | +used, otherwise `'static` is used. This means that (e.g.) `&'r |
| 54 | +Box<Trait>` would default to `&'r Box<Trait+'static>`, and `&'r |
| 55 | +Ref<'q, Trait>` (from `RefCell`) would default to `&'r Ref<'q, |
| 56 | +Trait+'q>`. |
| 57 | + |
| 58 | +### Problems with the current default. |
| 59 | + |
| 60 | +**Same types, different expansions.** One problem is fairly |
| 61 | +predictable: the current default means that identical types differ in |
| 62 | +their interpretation based on where they appear. This is something we |
| 63 | +have striven to avoid in general. So, as an example, this code |
| 64 | +[will not type-check](http://is.gd/Yaak1l): |
| 65 | + |
| 66 | +```rust |
| 67 | +trait Trait { } |
| 68 | + |
| 69 | +struct Foo { |
| 70 | + field: Box<Trait> |
| 71 | +} |
| 72 | + |
| 73 | +fn do_something(f: &mut Foo, x: &mut Box<Trait>) { |
| 74 | + mem::swap(&mut f.field, &mut *x); |
| 75 | +} |
| 76 | +``` |
| 77 | + |
| 78 | +Even though `x` is a reference to a `Box<Trait>` and the type of |
| 79 | +`field` is a `Box<Trait>`, the expansions differ. `x` expands to `&'x |
| 80 | +mut Box<Trait+'x>` and the field expands to `Box<Trait+'static>`. In |
| 81 | +general, we have tried to ensure that if the type is *typed precisely |
| 82 | +the same* in a type definition and a fn definition, then those two |
| 83 | +types are equal (note that fn definitions allow you to omit things |
| 84 | +that cannot be omitted in types, so some types that you can enter in a |
| 85 | +fn definition, like `&i32`, cannot appear in a type definition). |
| 86 | + |
| 87 | +Now, the same is of course true for the type `Trait` itself, which |
| 88 | +appears identically in different contexts and is expanded in different |
| 89 | +ways. This is not a problem here because the type `Trait` is unsized, |
| 90 | +which means that it cannot be swapped or moved, and hence the main |
| 91 | +sources of type mismatches are avoided. |
| 92 | + |
| 93 | +**Mental model.** In general the mental model of the newer rules seems |
| 94 | +simpler: once you move a trait object into the heap (via `Box`, or |
| 95 | +`Arc`), you must explicitly indicate whether it can contain borrowed |
| 96 | +data or not. So long as you manipulate by reference, you don't have |
| 97 | +to. In contrast, the current rules are more subtle, since objects in |
| 98 | +the heap may still accept borrowed data, if you have a reference to |
| 99 | +the box. |
| 100 | + |
| 101 | +**Poor interaction with the dropck rules.** When implementing the |
| 102 | +newer dropck rules specified by [RFC 769], we found a |
| 103 | +[rather subtle problem] that would arise with the current defaults. |
| 104 | +The precise problem is spelled out in appendix below, but the TL;DR is |
| 105 | +that if you wish to pass an array of boxed objects, the current |
| 106 | +defaults can be actively harmful, and hence force you to specify |
| 107 | +explicit lifetimes, whereas the newer defaults do something |
| 108 | +reasonable. |
| 109 | + |
| 110 | +# Detailed design |
| 111 | + |
| 112 | +The rules for user-defined types from RFC 599 are altered as follows |
| 113 | +(text that is not changed is italicized): |
| 114 | + |
| 115 | +- *If `SomeType` contains a single where-clause like `T:'a`, where |
| 116 | + `T` is some type parameter on `SomeType` and `'a` is some |
| 117 | + lifetime, then the type provided as value of `T` will have a |
| 118 | + default object bound of `'a`. An example of this is |
| 119 | + `std::cell::Ref`: a usage like `Ref<'x, X>` would change the |
| 120 | + default for object types appearing in `X` to be `'a`.* |
| 121 | +- If `SomeType` contains no where-clauses of the form `T:'a`, then |
| 122 | + the "base default" is used. The base default depends on the overall context: |
| 123 | + - in a fn body, the base default is a fresh inference variable. |
| 124 | + - outside of a fn body, such in a fn signature, the base default |
| 125 | + is `'static`. |
| 126 | + Hence `Box<X>` would typically be a default of `'static` for `X`, |
| 127 | + regardless of whether it appears underneath an `&` or not. |
| 128 | + (Note that in a fn body, the inference is strong enough to adopt `'static` |
| 129 | + if that is the necessary bound, or a looser bound if that would be helpful.) |
| 130 | +- *If `SomeType` contains multiple where-clauses of the form `T:'a`, |
| 131 | + then the default is cleared and explicit lifetiem bounds are |
| 132 | + required. There are no known examples of this in the standard |
| 133 | + library as this situation arises rarely in practice.* |
| 134 | + |
| 135 | +# Timing and breaking change implications |
| 136 | + |
| 137 | +This is a breaking change, and hence it behooves us to evaluate the |
| 138 | +impact and describe a procedure for making the change as painless as |
| 139 | +possible. One nice propery of this change is that it only affects |
| 140 | +*defaults*, which means that it is always possible to write code that |
| 141 | +compiles both before and after the change by avoiding defaults in |
| 142 | +those cases where the new and old compiler disagree. |
| 143 | + |
| 144 | +The estimated impact of this change is very low, for two reasons: |
| 145 | +- A recent test of crates.io found [no regressions][crater] caused by |
| 146 | + this change (however, a [previous run] (from before Rust 1.0) found 8 |
| 147 | + regressions). |
| 148 | +- This feature was only recently stabilized as part of Rust 1.0 (and |
| 149 | + was only added towards the end of the release cycle), so there |
| 150 | + hasn't been time for a large body of dependent code to arise |
| 151 | + outside of crates.io. |
| 152 | + |
| 153 | +Nonetheless, to minimize impact, this RFC proposes phasing in the |
| 154 | +change as follows: |
| 155 | + |
| 156 | +- In Rust 1.2, a warning will be issued for code which will break when the |
| 157 | + defaults are changed. This warning can be disabled by using explicit |
| 158 | + bounds. The warning will only be issued when explicit bounds would be required |
| 159 | + in the future anyway. |
| 160 | + - Specifically, types that were written `&Box<Trait>` where the |
| 161 | + (boxed) trait object may contain references should now be written |
| 162 | + `&Box<Trait+'a>` to disable the warning. |
| 163 | +- In Rust 1.3, the change will be made permanent. Any code that has |
| 164 | + not been updated by that time will break. |
| 165 | + |
| 166 | +# Drawbacks |
| 167 | + |
| 168 | +The primary drawback is that this is a breaking change, as discussed |
| 169 | +in the previous section. |
| 170 | + |
| 171 | +# Alternatives |
| 172 | + |
| 173 | +Keep the current design, with its known drawbacks. |
| 174 | + |
| 175 | +# Unresolved questions |
| 176 | + |
| 177 | +None. |
| 178 | + |
| 179 | +# Appendix: Details of the dropck problem |
| 180 | + |
| 181 | +This appendix goes into detail about the sticky interaction with |
| 182 | +dropck that was uncovered. The problem arises if you have a function |
| 183 | +that wishes to take a mutable slice of objects, like so: |
| 184 | + |
| 185 | +```rust |
| 186 | +fn do_it(x: &mut [Box<FnMut()>]) { ... } |
| 187 | +``` |
| 188 | + |
| 189 | +Here, `&mut [..]` is used because the objects are `FnMut` objects, and |
| 190 | +hence require `&mut self` to call. This function in turn is expanded |
| 191 | +to: |
| 192 | + |
| 193 | +```rust |
| 194 | +fn do_it<'x>(x: &'x mut [Box<FnMut()+'x>]) { ... } |
| 195 | +``` |
| 196 | + |
| 197 | +Now callers might try to invoke the function as so: |
| 198 | + |
| 199 | +```rust |
| 200 | +do_it(&mut [Box::new(val1), Box::new(val2)]) |
| 201 | +``` |
| 202 | + |
| 203 | +Unfortunately, this code fails to compile -- in fact, it cannot be |
| 204 | +made to compile without changing the definition of `do_it`, due to a |
| 205 | +sticky interaction between dropck and variance. The problem is that |
| 206 | +dropck requires that all data in the box strictly outlives the |
| 207 | +lifetime of the box's owner. This is to prevent cyclic |
| 208 | +content. Therefore, the type of the objects must be `Box<FnMut()+'R>` |
| 209 | +where `'R` is some region that strictly outlives the array itself (as |
| 210 | +the array is the owner of the objects). However, the signature of |
| 211 | +`do_it` demands that the reference to the array has the same lifetime |
| 212 | +as the trait objects within (and because this is an `&mut` reference |
| 213 | +and hence invariant, no approximation is permitted). This implies that |
| 214 | +the array must live for at least the region `'R`. But we defined the |
| 215 | +region `'R` to be some region that outlives the array, so we have a |
| 216 | +quandry. |
| 217 | + |
| 218 | +The solution is to change the definition of `do_it` in one of two |
| 219 | +ways: |
| 220 | + |
| 221 | +```rust |
| 222 | +// Use explicit lifetimes to make it clear that the reference is not |
| 223 | +// required to have the same lifetime as the objects themselves: |
| 224 | +fn do_it1<'a,'b>(x: &'a mut [Box<FnMut()+'b>]) { ... } |
| 225 | + |
| 226 | +// Specifying 'static is easier, but then the closures cannot |
| 227 | +// capture the stack: |
| 228 | +fn do_it2(x: &'a mut [Box<FnMut()+'static>]) { ... } |
| 229 | +``` |
| 230 | + |
| 231 | +Under the proposed RFC, `do_it2` would be the default. If one wanted |
| 232 | +to use lifetimes, then one would have to use explicit lifetime |
| 233 | +overrides as shown in `do_it1`. This is consistent with the mental |
| 234 | +model of "once you box up an object, you must add annotations for it |
| 235 | +to contain borrowed data". |
| 236 | + |
| 237 | +[RFC 599]: 0599-default-object-bound.md |
| 238 | +[RFC 769]: 0769-sound-generic-drop.md |
| 239 | +[rather subtle problem]: https://github.com/rust-lang/rust/pull/25212#issuecomment-100244929 |
| 240 | +[crater]: https://gist.github.com/brson/085d84d43c6a9a8d4dc3 |
| 241 | +[branch]: https://github.com/nikomatsakis/rust/tree/better-object-defaults |
| 242 | +[previous run]: https://gist.github.com/brson/80f9b80acef2e7ab37ee |
| 243 | +[RFC 1122]: https://github.com/rust-lang/rfcs/pull/1122 |
0 commit comments