diff --git a/atomics.md b/atomics.md index 69c0874..39ea215 100644 --- a/atomics.md +++ b/atomics.md @@ -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 diff --git a/references.md b/references.md index fa47a69..1039cb1 100644 --- a/references.md +++ b/references.md @@ -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.