Skip to content

Commit e17464c

Browse files
committed
Adjust policy text to include warnings in 1.2, hard error in 1.3,
and remove the legacy attribute (also adjust text of RFC #1122 slightly).
1 parent 317885e commit e17464c

File tree

2 files changed

+262
-9
lines changed

2 files changed

+262
-9
lines changed
+243
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: (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

text/1122-language-semver.md

+19-9
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,8 @@ decision. In general, it is best to find ways to correct such errors
242242
without making breaking changes, such as improved error messages or
243243
deprecation. However, if the impact of making the change is judged to
244244
be negligible, we can also consider fixing the problem, presuming that
245-
the following criteria are met:
245+
the following criteria are met (in addition to the criteria listed
246+
above in the section "Other Factors to Consider"):
246247

247248
- All data indicates that correcting this flaw will break extremely little
248249
or no existing code (such as crates.io testing, communication with production
@@ -255,12 +256,23 @@ the following criteria are met:
255256
soundness reasons).
256257
- There is no backwards compatible way to repair the problem.
257258

258-
Naturally, all of the concerns listed above in the section "Other
259-
Factors to Consider" also apply here. For example, we should consider
260-
the quality of the error messages that result from the breaking
261-
change, and evaluate whether it is possible to write code that works
262-
both before/after the change (which enables users to span compiler
263-
versions).
259+
When we do decide to make such a change, we should follow one of the
260+
following ways to ease the transition, in order of preference:
261+
262+
1. When possible, issue a release that gives warnings when changes will be required.
263+
- These warnings should be as targeted as possible -- if the warning
264+
is too broad, it can easily cause more annoyance than the change
265+
itself.
266+
- To maximize the chance of these warnings being taken seriously,
267+
the warning should not be a lint. The only way to disable it is to
268+
make a change that will be forwards compatible with the new
269+
version.
270+
- After a suitable time has elapsed (at least one cycle, possibly
271+
more), make the actual change.
272+
2. If warnings are not possible, then include a `#[legacy]` attribute
273+
that allows the old behavior to be restored.
274+
- Ideally, this `#[legacy]` attribute will only persist for a fixed
275+
number of cycles.
264276

265277
# Drawbacks
266278

@@ -269,8 +281,6 @@ even when done with the best of intentions. The alternatives list some
269281
ways that we could avoid breaking changes altogether, and the
270282
downsides of each.
271283

272-
## Notes on phasing
273-
274284
# Alternatives
275285

276286
**Rather than simply fixing soundness bugs, we could issue new major

0 commit comments

Comments
 (0)