Skip to content

Commit 7fabfeb

Browse files
committed
Initial draft
1 parent 69a0f68 commit 7fabfeb

File tree

1 file changed

+118
-0
lines changed

1 file changed

+118
-0
lines changed

active/0000-box-not-special.md

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
- Start Date: (fill me in with today's date, YYYY-MM-DD)
2+
- RFC PR #: (leave this empty)
3+
- Rust Issue #: (leave this empty)
4+
5+
# Summary
6+
7+
Remove special treatment of `Box<T>` from the borrow checker.
8+
9+
# Motivation
10+
11+
Currently the `Box<T>` type is special-cased and converted to the old
12+
`~T` internally. This is mostly invisible to the user, but it shows up
13+
in some places that give special treatment to `Box<T>`. This RFC is
14+
specifically concerned with the fact that the borrow checker has
15+
greater precision when derefencing `Box<T>` vs other smart pointers
16+
that rely on the `Deref` traits. Unlike the other kinds of special
17+
treatment, we do not currently have a plan for how to extend this
18+
behavior to all smart pointer types, and hence we would like to remove
19+
it.
20+
21+
Here is an example that illustrates the extra precision afforded to
22+
`Box<T>` vs other types that implement the `Deref` traits. The
23+
following program, written using the `Box` type, compiles
24+
successfully:
25+
26+
struct Pair {
27+
a: uint,
28+
b: uint
29+
}
30+
31+
fn example1(mut smaht: Box<Pair>) {
32+
let a = &mut smaht.a;
33+
let b = &mut smaht.b;
34+
...
35+
}
36+
37+
This program compiles because the type checker can see that
38+
`(*smaht).a` and `(*smaht).b` are always distinct paths. In contrast,
39+
if I use a smart pointer, I get compilation errors:
40+
41+
fn example2(cell: RefCell<Pair>) {
42+
let mut smaht: RefMut<Pair> = cell.borrow_mut();
43+
let a = &mut smaht.a;
44+
45+
// Error: cannot borrow `smaht` as mutable more than once at a time
46+
let b = &mut smaht.b;
47+
}
48+
49+
To see why this, consider the desugaring:
50+
51+
fn example2(smaht: RefCell<Pair>) {
52+
let mut smaht = smaht.borrow_mut();
53+
54+
let tmp1: &mut Pair = smaht.deref_mut(); // borrows `smaht`
55+
let a = &mut tmp1.a;
56+
57+
let tmp2: &mut Pair = smaht.deref_mut(); // borrows `smaht` again!
58+
let b = &mut tmp2.b;
59+
}
60+
61+
It is a violation of the Rust type system to invoke `deref_mut` when
62+
the reference to `a` is valid and usable, since `deref_mut` requires
63+
`&mut self`, which in turn implies no alias to `self` or anything
64+
owned by `self`.
65+
66+
This desugaring suggests how the problem can be worked around in user
67+
code. The idea is to pull the result of the deref into a new temporary:
68+
69+
fn example3(smaht: RefCell<Pair>) {
70+
let mut smaht: RefMut<Pair> = smaht.borrow_mut();
71+
let temp: &mut Pair = &mut *smaht;
72+
let a = &mut temp.a;
73+
let b = &mut temp.b;
74+
}
75+
76+
# Detailed design
77+
78+
Removing this treatment from the borrow checker basically means
79+
changing the construction of loan paths for unique pointers.
80+
81+
I don't actually know how best to implement this in the borrow
82+
checker, particularly concerning the desire to keep the ability to
83+
move out of boxes and use them in patterns. This requires some
84+
investigation. The easiest and best way may be to "do it right" and is
85+
probably to handle derefs of `Box<T>` in a similar way to how
86+
overloaded derefs are handled, but somewhat differently to account for
87+
the possibility of moving out of them. Some investigation is needed.
88+
89+
# Drawbacks
90+
91+
The borrow checker rules are that much more restrictive.
92+
93+
# Alternatives
94+
95+
We have ruled out inconsistent behavior between `Box` and other smart
96+
pointer types. We considered a number of ways to extend the current
97+
treatment of box to other smart pointer types:
98+
99+
1. *Require* compiler to introduce deref temporaries automatically
100+
where possible. This is plausible as a future extension but
101+
requires some thought to work through all cases. It may be
102+
surprising. Note that this would be a required optimization because
103+
if the optimization is not performed it affects what programs can
104+
successfully type check. (Naturally it is also observable.)
105+
106+
2. Some sort of unsafe deref trait that acknolwedges possibliity of
107+
other pointers into the referent. Unappealing because the problem
108+
is not that bad as to require unsafety.
109+
110+
3. Determining conditions (perhaps based on parametricity?) where it
111+
is provably safe to call deref. It is dubious and unknown if such
112+
conditions exist or what that even means. Rust also does not really
113+
enjoy parametricity properties due to presence of reflection and
114+
unsafe code.
115+
116+
# Unresolved questions
117+
118+
Best implementation strategy.

0 commit comments

Comments
 (0)