|
| 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