Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC to remove some special treatment of Box by borrow checker #130

Merged
merged 1 commit into from
Jul 30, 2014
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions active/0000-box-not-special.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
- Start Date: (fill me in with today's date, YYYY-MM-DD)
- RFC PR #: (leave this empty)
- Rust Issue #: (leave this empty)

# Summary

Remove special treatment of `Box<T>` from the borrow checker.

# Motivation

Currently the `Box<T>` type is special-cased and converted to the old
`~T` internally. This is mostly invisible to the user, but it shows up
in some places that give special treatment to `Box<T>`. This RFC is
specifically concerned with the fact that the borrow checker has
greater precision when derefencing `Box<T>` vs other smart pointers
that rely on the `Deref` traits. Unlike the other kinds of special
treatment, we do not currently have a plan for how to extend this
behavior to all smart pointer types, and hence we would like to remove
it.

Here is an example that illustrates the extra precision afforded to
`Box<T>` vs other types that implement the `Deref` traits. The
following program, written using the `Box` type, compiles
successfully:

struct Pair {
a: uint,
b: uint
}

fn example1(mut smaht: Box<Pair>) {
let a = &mut smaht.a;
let b = &mut smaht.b;
...
}

This program compiles because the type checker can see that
`(*smaht).a` and `(*smaht).b` are always distinct paths. In contrast,
if I use a smart pointer, I get compilation errors:

fn example2(cell: RefCell<Pair>) {
let mut smaht: RefMut<Pair> = cell.borrow_mut();
let a = &mut smaht.a;

// Error: cannot borrow `smaht` as mutable more than once at a time
let b = &mut smaht.b;
}

To see why this, consider the desugaring:

fn example2(smaht: RefCell<Pair>) {
let mut smaht = smaht.borrow_mut();

let tmp1: &mut Pair = smaht.deref_mut(); // borrows `smaht`
let a = &mut tmp1.a;

let tmp2: &mut Pair = smaht.deref_mut(); // borrows `smaht` again!
let b = &mut tmp2.b;
}

It is a violation of the Rust type system to invoke `deref_mut` when
the reference to `a` is valid and usable, since `deref_mut` requires
`&mut self`, which in turn implies no alias to `self` or anything
owned by `self`.

This desugaring suggests how the problem can be worked around in user
code. The idea is to pull the result of the deref into a new temporary:

fn example3(smaht: RefCell<Pair>) {
let mut smaht: RefMut<Pair> = smaht.borrow_mut();
let temp: &mut Pair = &mut *smaht;
let a = &mut temp.a;
let b = &mut temp.b;
}

# Detailed design

Removing this treatment from the borrow checker basically means
changing the construction of loan paths for unique pointers.

I don't actually know how best to implement this in the borrow
checker, particularly concerning the desire to keep the ability to
move out of boxes and use them in patterns. This requires some
investigation. The easiest and best way may be to "do it right" and is
probably to handle derefs of `Box<T>` in a similar way to how
overloaded derefs are handled, but somewhat differently to account for
the possibility of moving out of them. Some investigation is needed.

# Drawbacks

The borrow checker rules are that much more restrictive.

# Alternatives

We have ruled out inconsistent behavior between `Box` and other smart
pointer types. We considered a number of ways to extend the current
treatment of box to other smart pointer types:

1. *Require* compiler to introduce deref temporaries automatically
where possible. This is plausible as a future extension but
requires some thought to work through all cases. It may be
surprising. Note that this would be a required optimization because
if the optimization is not performed it affects what programs can
successfully type check. (Naturally it is also observable.)

2. Some sort of unsafe deref trait that acknolwedges possibliity of
other pointers into the referent. Unappealing because the problem
is not that bad as to require unsafety.

3. Determining conditions (perhaps based on parametricity?) where it
is provably safe to call deref. It is dubious and unknown if such
conditions exist or what that even means. Rust also does not really
enjoy parametricity properties due to presence of reflection and
unsafe code.

# Unresolved questions

Best implementation strategy.