niko discussion affects
This commit is contained in:
parent
fcf4a7e5c8
commit
29e71b92bc
@ -24,7 +24,7 @@ do exactly what we said but, you know, *fast*. Wouldn't that be great?
|
||||
# Compiler Reordering
|
||||
|
||||
Compilers fundamentally want to be able to do all sorts of crazy transformations
|
||||
to reduce data dependencies and eleminate dead code. In particular, they may
|
||||
to reduce data dependencies and eliminate dead code. In particular, they may
|
||||
radically change the actual order of events, or make events never occur! If we
|
||||
write something like
|
||||
|
||||
|
@ -25,14 +25,16 @@ as its direct children. Each variable's direct children would be their fields
|
||||
|
||||
From this view, every value in Rust has a unique *path* in the tree of ownership.
|
||||
References to a value can subsequently be interpreted as a path in this tree.
|
||||
Of particular interest are *prefixes*: `x` is a prefix of `y` if `x` owns `y`
|
||||
Of particular interest are *ancestors* and *descendants*: if `x` owns `y`, then
|
||||
`x` is an *ancestor* of `y`, and `y` is a *descendant* of `x`. Note that this is
|
||||
an inclusive relationship: `x` is a descendant and ancestor of itself.
|
||||
|
||||
However much data doesn't reside on the stack, and we must also accommodate this.
|
||||
Tragically, plenty of data doesn't reside on the stack, and we must also accommodate this.
|
||||
Globals and thread-locals are simple enough to model as residing at the bottom
|
||||
of the stack (though we must be careful with mutable globals). Data on
|
||||
the heap poses a different problem.
|
||||
|
||||
If all Rust had on the heap was data uniquely by a pointer on the stack,
|
||||
If all Rust had on the heap was data uniquely owned by a pointer on the stack,
|
||||
then we can just treat that pointer as a struct that owns the value on
|
||||
the heap. Box, Vec, String, and HashMap, are examples of types which uniquely
|
||||
own data on the heap.
|
||||
@ -51,6 +53,10 @@ types provide exclusive access through runtime restrictions. However it is also
|
||||
possible to establish unique ownership without interior mutability. For instance,
|
||||
if an Rc has refcount 1, then it is safe to mutate or move its internals.
|
||||
|
||||
In order to correctly communicate to the type system that a variable or field of
|
||||
a struct can have interior mutability, it must be wrapped in an UnsafeCell. This
|
||||
does not in itself make it safe to perform interior mutability operations on that
|
||||
value. You still must yourself ensure that mutual exclusion is upheld.
|
||||
|
||||
|
||||
|
||||
@ -61,9 +67,9 @@ dereferenced. Shared references are always live unless they are literally unreac
|
||||
(for instance, they reside in freed or leaked memory). Mutable references can be
|
||||
reachable but *not* live through the process of *reborrowing*.
|
||||
|
||||
A mutable reference can be reborrowed to either a shared or mutable reference.
|
||||
Further, the reborrow can produce exactly the same reference, or point to a
|
||||
path it is a prefix of. For instance, a mutable reference can be reborrowed
|
||||
A mutable reference can be reborrowed to either a shared or mutable reference to
|
||||
one of its descendants. A reborrowed reference will only be live again once all
|
||||
reborrows derived from it expire. For instance, a mutable reference can be reborrowed
|
||||
to point to a field of its referent:
|
||||
|
||||
```rust
|
||||
@ -79,7 +85,7 @@ let x = &mut (1, 2);
|
||||
```
|
||||
|
||||
It is also possible to reborrow into *multiple* mutable references, as long as
|
||||
they are *disjoint*: no reference is a prefix of another. Rust
|
||||
they are *disjoint*: no reference is an ancestor of another. Rust
|
||||
explicitly enables this to be done with disjoint struct fields, because
|
||||
disjointness can be statically proven:
|
||||
|
||||
@ -89,6 +95,7 @@ let x = &mut (1, 2);
|
||||
// reborrow x to two disjoint subfields
|
||||
let y = &mut x.0;
|
||||
let z = &mut x.1;
|
||||
|
||||
// y and z are now live, but x isn't
|
||||
*y = 3;
|
||||
*z = 4;
|
||||
@ -105,14 +112,14 @@ To simplify things, we can model variables as a fake type of reference: *owned*
|
||||
references. Owned references have much the same semantics as mutable references:
|
||||
they can be re-borrowed in a mutable or shared manner, which makes them no longer
|
||||
live. Live owned references have the unique property that they can be moved
|
||||
out of (though mutable references *can* be swapped out of). This is
|
||||
out of (though mutable references *can* be swapped out of). This power is
|
||||
only given to *live* owned references because moving its referent would of
|
||||
course invalidate all outstanding references prematurely.
|
||||
|
||||
As a local lint against inappropriate mutation, only variables that are marked
|
||||
as `mut` can be borrowed mutably.
|
||||
|
||||
It is also interesting to note that Box behaves exactly like an owned
|
||||
It is interesting to note that Box behaves exactly like an owned
|
||||
reference. It can be moved out of, and Rust understands it sufficiently to
|
||||
reason about its paths like a normal variable.
|
||||
|
||||
@ -123,8 +130,12 @@ reason about its paths like a normal variable.
|
||||
|
||||
With liveness and paths defined, we can now properly define *aliasing*:
|
||||
|
||||
**A mutable reference is aliased if there exists another live reference to it or
|
||||
one of its prefixes.**
|
||||
**A mutable reference is aliased if there exists another live reference to one of
|
||||
its ancestors or descendants.**
|
||||
|
||||
(If you prefer, you may also say the two live references alias *each other*.
|
||||
This has no semantic consequences, but is probably a more useful notion when
|
||||
verifying the soundness of a construct.)
|
||||
|
||||
That's it. Super simple right? Except for the fact that it took us two pages
|
||||
to define all of the terms in that defintion. You know: Super. Simple.
|
||||
|
Loading…
x
Reference in New Issue
Block a user