Skip to content

Commit 9a660cb

Browse files
committed
Merge branch 'nikomatsakis-adjust-default-object-bounds' into HEAD
2 parents bdf52a8 + b754769 commit 9a660cb

File tree

2 files changed

+244
-0
lines changed

2 files changed

+244
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ the direction the language is evolving in.
5858
* [1096-remove-static-assert.md](text/1096-remove-static-assert.md)
5959
* [1122-language-semver.md](text/1122-language-semver.md)
6060
* [1131-likely-intrinsic.md](text/1131-likely-intrinsic.md)
61+
* [1156-adjust-default-object-bounds.md](text/1156-adjust-default-object-bounds.md)
6162

6263
## Table of Contents
6364
[Table of Contents]: #table-of-contents
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
- Feature Name: N/A
2+
- Start Date: 2015-06-4
3+
- RFC PR: https://github.com/rust-lang/rfcs/pull/1156
4+
- Rust Issue: https://github.com/rust-lang/rust/issues/26438
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

Comments
 (0)