diff --git a/lifetimes.md b/lifetimes.md index 350a4ea..f4475b7 100644 --- a/lifetimes.md +++ b/lifetimes.md @@ -19,7 +19,7 @@ our toes with lifetimes, we're going to pretend that we're actually allowed to label scopes with lifetimes, and desugar the examples from the start of this chapter. -Our examples made use of *aggressive* sugar -- high fructose corn syrup even -- +Originally, our examples made use of *aggressive* sugar -- high fructose corn syrup even -- around scopes and lifetimes, because writing everything out explicitly is *extremely noisy*. All Rust code relies on aggressive inference and elision of "obvious" things. @@ -166,7 +166,7 @@ our implementation *just a bit*.) -# Example 2: aliasing a mutable reference +# Example: aliasing a mutable reference How about the other example: diff --git a/references.md b/references.md index a109bf5..da70dd5 100644 --- a/references.md +++ b/references.md @@ -1,5 +1,12 @@ % References +This section gives a high-level view of the memory model that *all* Rust +programs must satisfy to be correct. Safe code is statically verified +to obey this model by the borrow checker. Unsafe code may go above +and beyond the borrow checker while still satisfying this model. The borrow +checker may also be extended to allow more programs to compile, as long as +this more fundamental model is satisfied. + There are two kinds of reference: * Shared reference: `&` @@ -7,53 +14,63 @@ There are two kinds of reference: Which obey the following rules: -* A reference cannot outlive its referent A mutable reference cannot be aliased +* A reference cannot outlive its referent +* A mutable reference cannot be aliased -To define aliasing, we must define the notion of *paths* and *liveness*. +That's it. That's the whole model. Of course, we should probably define +what *aliased* means. To define aliasing, we must define the notion of +*paths* and *liveness*. # Paths -If all Rust had were values, then every value would be uniquely owned by a -variable or composite structure. From this we naturally derive a *tree* of -ownership. The stack itself is the root of the tree, with every variable as its -direct children. Each variable's direct children would be their fields (if any), -and so on. +If all Rust had were values (no pointers), then every value would be uniquely +owned by a variable or composite structure. From this we naturally derive a +*tree* of ownership. The stack itself is the root of the tree, with every +variable as its direct children. Each variable's direct children would be their +fields (if any), and so on. 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 *ancestors* and *descendants*: if `x` owns -`y`, then `x` is an *ancestor* of `y`, and `y` is a *descendant* of `x`. Note +ownership. 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. +We can then define references as simply *names* for paths. When you create a +reference, you're declaring that an ownership path exists to this address +of memory. + 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 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. +then we could just treat such a 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. Unfortunately, data on the heap is not *always* uniquely owned. Rc for instance -introduces a notion of *shared* ownership. Shared ownership means there is no -unique path. A value with no unique path limits what we can do with it. In -general, only shared references can be created to these values. However +introduces a notion of *shared* ownership. Shared ownership of a value means +there is no unique path to it. A value with no unique path limits what we can do +with it. + +In general, only shared references can be created to non-unique paths. However mechanisms which ensure mutual exclusion may establish One True Owner -temporarily, establishing a unique path to that value (and therefore all its -children). +temporarily, establishing a unique path to that value (and therefore all +its children). If this is done, the value may be mutated. In particular, a +mutable reference can be taken. The most common way to establish such a path is through *interior mutability*, in contrast to the *inherited mutability* that everything in Rust normally uses. Cell, RefCell, Mutex, and RWLock are all examples of interior mutability types. -These 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. +These types provide exclusive access through runtime restrictions. + +An interesting case of this effect is Rc itself: if an Rc has refcount 1, +then it is safe to mutate or even move its internals. Note however that the +refcount itself uses interior mutability. 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 @@ -62,6 +79,7 @@ that value. You still must yourself ensure that mutual exclusion is upheld. + # Liveness Note: Liveness is not the same thing as a *lifetime*, which will be explained diff --git a/subtyping.md b/subtyping.md index 1b33c9a..8c5ac9c 100644 --- a/subtyping.md +++ b/subtyping.md @@ -100,7 +100,7 @@ subsequently be dropped, and `forever_str` would point to freed memory when we print it! Therefore `&mut` should be invariant. This is the general theme of variance vs invariance: if variance would allow you -to *store* a short-lived value over a longer-lived slot, then you must be +to store a short-lived value into a longer-lived slot, then you must be invariant. However it *is* sound for `&'a mut T` to be variant over `'a`. The key difference