From: Ralf Jung Date: Sun, 17 Jan 2016 14:13:38 +0000 (+0100) Subject: terminology: &T and &mut T are the types of *references*. Also, stress the exclusivit... X-Git-Url: https://git.ralfj.de/rust-101.git/commitdiff_plain/63b7a79069a399c32d575bb71832fcad134117c9?ds=inline;hp=55d7df9f941d9979b568d094930c5009731737cf terminology: &T and &mut T are the types of *references*. Also, stress the exclusivity of &mut better, and use it to explain interior mutability. --- diff --git a/src/main.rs b/src/main.rs index 8fbc933..9b7132d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -65,7 +65,7 @@ // // ### Basic Rust // -// * [Part 04: Ownership, Borrowing](part04.html) +// * [Part 04: Ownership, Borrowing, References](part04.html) // * [Part 05: Clone](part05.html) // * [Part 06: Copy, Lifetimes](part06.html) // * [Part 07: Operator Overloading, Tests, Formating](part07.html) diff --git a/src/part04.rs b/src/part04.rs index e7cf7f2..fcfc8d9 100644 --- a/src/part04.rs +++ b/src/part04.rs @@ -1,5 +1,5 @@ -// Rust-101, Part 04: Ownership, Borrowing -// ======================================= +// Rust-101, Part 04: Ownership, Borrowing, References +// =================================================== //@ Rust aims to be a "safe systems language". As a systems language, of course it //@ provides *references* (or *pointers*). But as a safe language, it has to @@ -43,33 +43,33 @@ fn ownership_demo() { //@ Essentially, ownership rules out aliasing, hence making the kind of problem discussed above //@ impossible. -// ## Shared borrowing +// ## Borrowing a shared reference //@ If you go back to our example with `vec_min`, and try to call that function twice, you will //@ get the same error. That's because `vec_min` demands that the caller transfers ownership of the //@ vector. Hence, when `vec_min` finishes, the entire vector is deleted. That's of course not what //@ we wanted! Can't we somehow give `vec_min` access to the vector, while retaining ownership of it? //@ -//@ Rust calls this *borrowing* the vector, and it works a bit like borrowing does in the real world: -//@ If you borrow a book to your friend, your friend can have it and work on it (and you can't!) -//@ as long as the book is still borrowed. Your friend could even borrow the book to someone else. -//@ Eventually however, your friend has to give the book back to you, at which point you again -//@ have full control. +//@ Rust calls this *a reference* the vector, and it considers references as *borrowing* ownership. This +//@ works a bit like borrowing does in the real world: If you borrow a book to your friend, your friend +//@ can have it and work on it (and you can't!) as long as the book is still borrowed. Your friend could +//@ even borrow the book to someone else. Eventually however, your friend has to give the book back to you, +//@ at which point you again have full control. //@ -//@ Rust distinguishes between two kinds of borrows. First of all, there's the *shared* borrow. -//@ This is where the book metaphor kind of breaks down... you can give a shared borrow of +//@ Rust distinguishes between two kinds of references. First of all, there's the *shared* reference. +//@ This is where the book metaphor kind of breaks down... you can give a shared reference to //@ *the same data* to lots of different people, who can all access the data. This of course -//@ introduces aliasing, so in order to live up to its promise of safety, Rust does not allow -//@ mutation through a shared borrow. +//@ introduces aliasing, so in order to live up to its promise of safety, Rust generally does not allow +//@ mutation through a shared reference. -//@ So, let's re-write `vec_min` to work on a shared borrow of a vector, written `&Vec`. +//@ So, let's re-write `vec_min` to work on a shared reference to a vector, written `&Vec`. //@ I also took the liberty to convert the function from `SomethingOrNothing` to the standard //@ library type `Option`. fn vec_min(v: &Vec) -> Option { use std::cmp; let mut min = None; - // This time, we explicitly request an iterator for the vector `v`. The method `iter` borrows the vector - // it works on, and provides shared borrows of the elements. + // This time, we explicitly request an iterator for the vector `v`. The method `iter` just borrows the vector + // it works on, and provides shared references to the elements. for e in v.iter() { // In the loop, `e` now has type `&i32`, so we have to dereference it to obtain an `i32`. min = Some(match min { @@ -81,64 +81,64 @@ fn vec_min(v: &Vec) -> Option { } // Now that `vec_min` does not acquire ownership of the vector anymore, we can call it multiple times on the same vector and also do things like -fn shared_borrow_demo() { +fn shared_ref_demo() { let v = vec![5,4,3,2,1]; let first = &v[0]; vec_min(&v); vec_min(&v); println!("The first element is: {}", *first); } -//@ What's going on here? First, `&` is how you create a shared borrow to something. All borrows are created like -//@ this - there is no way to have something like a NULL pointer. This code creates three shared borrows to `v`: -//@ The borrow for `first` begins in the 2nd line of the function and lasts all the way to the end. The other two -//@ borrows, created for calling `vec_min`, only last for the duration of that respective call. +//@ What's going on here? First, `&` is how you borrow ownership to someone - this operator creates a shared reference. +//@ `shared_ref_demo` creates three shared references to `v`: +//@ The reference `first` begins in the 2nd line of the function and lasts all the way to the end. The other two +//@ references, created for calling `vec_min`, only last for the duration of that respective call. //@ -//@ Technically, of course, borrows are pointers. Notice that since `vec_min` only gets a shared -//@ borrow, Rust knows that it cannot mutate `v` in any way. Hence the pointer into the buffer of `v` +//@ Technically, of course, references are pointers. Notice that since `vec_min` only gets a shared +//@ reference, Rust knows that it cannot mutate `v`. Hence the pointer into the buffer of `v` //@ that was created before calling `vec_min` remains valid. -// ## Mutable borrowing -//@ There is a second kind of borrow, a *mutable borrow*. As the name suggests, such a borrow permits -//@ mutation, and hence has to prevent aliasing. There can only ever be one mutable borrow to a -//@ particular piece of data. +// ## Exclusive, mutable references +//@ There is a second way to borrow something, a second kind of reference: The *exclusive reference*. This is a reference that comes with the promise that +//@ nobody else has *any kind of access* to the referee - there is no aliasing. As a consequence, it is always safe to mutate data through +//@ an exclusive reference, which is why they are usually called *mutable references*. //@ As an example, consider a function which increments every element of a vector by 1. -//@ The type `&mut Vec` is the type of mutable borrows of `vec`. Because the borrow is -//@ mutable, we can use a mutable iterator, providing a mutable borrow of the elements. +//@ The type `&mut Vec` is the type of mutable references to `vec`. Because the reference is +//@ mutable, we can use a mutable iterator, providing mutable (exclusive) references to the elements. fn vec_inc(v: &mut Vec) { for e in v.iter_mut() { *e += 1; } } // Here's an example of calling `vec_inc`. -fn mutable_borrow_demo() { +fn mutable_ref_demo() { let mut v = vec![5,4,3,2,1]; /* let first = &v[0]; */ vec_inc(&mut v); vec_inc(&mut v); /* println!("The first element is: {}", *first); */ /* BAD! */ } -//@ `&mut` is the operator to create a mutable borrow. We have to mark `v` as mutable in order to create such a -//@ borrow: Even though we completely own `v`, Rust tries to protect us from accidentally mutating things. -//@ Hence owned variables that you intend to mutate, have to be annotated with `mut`. -//@ Because the borrow passed to `vec_inc` only lasts as long as the function call, we can still call -//@ `vec_inc` on the same vector twice: The durations of the two borrows do not overlap, so we never have more -//@ than one mutable borrow. However, we can *not* create a shared borrow that spans a call to `vec_inc`. Just try +//@ `&mut` is the operator to create a mutable reference. We have to mark `v` as mutable in order to create such a +//@ reference: Even though we completely own `v`, Rust tries to protect us from accidentally mutating things. +//@ Hence owned variables that you intend to mutate have to be annotated with `mut`. +//@ Because the reference passed to `vec_inc` only lasts as long as the function call, we can still call +//@ `vec_inc` on the same vector twice: The durations of the two references do not overlap, so we never have more +//@ than one mutable reference - we only ever borrow `v` once at a time. However, we can *not* create a shared reference that spans a call to `vec_inc`. Just try //@ enabling the commented-out lines, and watch Rust complain. This is because `vec_inc` could mutate -//@ the vector structurally (i.e., it could add or remove elements), and hence the pointer `first` +//@ the vector structurally (i.e., it could add or remove elements), and hence the reference `first` //@ could become invalid. In other words, Rust keeps us safe from bugs like the one in the C++ snipped above. //@ -//@ Above, I said that having a mutable borrow excludes aliasing. But if you look at the code above carefully, -//@ you may say: "Wait! Don't the `v` in `mutable_borrow_demo` and the `v` in `vec_inc` alias?" And you are right, -//@ they do. However, the `v` in `mutable_borrow_demo` is not actually usable, it is not *active*: As long as there is an -//@ outstanding borrow, Rust will not allow you to do anything with `v`. +//@ Above, I said that having a mutable reference excludes aliasing. But if you look at the code above carefully, +//@ you may say: "Wait! Don't the `v` in `mutable_ref_demo` and the `v` in `vec_inc` alias?" And you are right, +//@ they do. However, the `v` in `mutable_ref_demo` is not actually usable, it is not *active*: As long as `v` is +//@ borrowed, Rust will not allow you to do anything with it. // ## Summary // The ownership and borrowing system of Rust enforces the following three rules: // // * There is always exactly one owner of a piece of data -// * If there is an active mutable borrow, then nobody else can have active access to the data -// * If there is an active shared borrow, then every other active access to the data is also a shared borrow +// * If there is an active mutable reference, then nobody else can have active access to the data +// * If there is an active shared reference, then every other active access to the data is also a shared reference // // As it turns out, combined with the abstraction facilities of Rust, this is a very powerful mechanism // to tackle many problems beyond basic memory safety. You will see some examples for this soon. diff --git a/src/part05.rs b/src/part05.rs index a0eb1d1..71d712a 100644 --- a/src/part05.rs +++ b/src/part05.rs @@ -66,13 +66,13 @@ impl BigInt { //@ consumes the vector `v`. The caller hence loses access to its vector. However, there is something //@ we can do if we don't want that to happen: We can explicitly `clone` the vector, //@ which means that a full (or *deep*) copy will be performed. Technically, -//@ `clone` takes a borrowed vector, and returns a fully owned one. +//@ `clone` takes a borrowed vector in the form of a shared reference, and returns a fully owned one. fn clone_demo() { let v = vec![0,1 << 16]; let b1 = BigInt::from_vec((&v).clone()); let b2 = BigInt::from_vec(v); } -//@ Rust has special treatment for methods that borrow its `self` argument (like `clone`, or +//@ Rust has special treatment for methods that borrow their `self` argument (like `clone`, or //@ like `test_invariant` above): It is not necessary to explicitly borrow the receiver of the //@ method. Hence you could replace `(&v).clone()` by `v.clone()` above. Just try it! @@ -113,18 +113,18 @@ impl Clone for SomethingOrNothing { //@ `#[derive(Clone)]` right before the definition of `SomethingOrNothing`. // **Exercise 05.2**: Write some more functions on `BigInt`. What about a function that returns the number of -// digits? The number of non-zero digits? The smallest/largest digit? Of course, these should all just borrow `self`. +// digits? The number of non-zero digits? The smallest/largest digit? Of course, these should all take `self` as a shared reference (i.e., in borrowed form). // ## Mutation + aliasing considered harmful (part 2) -//@ Now that we know how to borrow a part of an `enum` (like `v` above), there's another example for why we +//@ Now that we know how to create references to contents of an `enum` (like `v` above), there's another example we can look at for why we //@ have to rule out mutation in the presence of aliasing. First, we define an `enum` that can hold either //@ a number, or a string. enum Variant { Number(i32), Text(String), } -//@ Now consider the following piece of code. Like above, `n` will be a borrow of a part of `var`, -//@ and since we wrote `ref mut`, the borrow will be mutable. In other words, right after the match, `ptr` +//@ Now consider the following piece of code. Like above, `n` will be a reference to a part of `var`, +//@ and since we wrote `ref mut`, the reference will be exclusive and mutable. In other words, right after the match, `ptr` //@ points to the number that's stored in `var`, where `var` is a `Number`. Remember that `_` means //@ "we don't care". fn work_on_variant(mut var: Variant, text: String) { diff --git a/src/part06.rs b/src/part06.rs index 5be00aa..8f9a723 100644 --- a/src/part06.rs +++ b/src/part06.rs @@ -28,7 +28,7 @@ impl BigInt { // Now we can write `vec_min`. fn vec_min(v: &Vec) -> Option { let mut min: Option = None; - // If `v` is a shared borrowed vector, then the default for iterating over it is to call `iter`, the iterator that borrows the elements. + // If `v` is a shared reference to a vector, then the default for iterating over it is to call `iter`, the iterator that borrows the elements. for e in v { let e = e.clone(); min = Some(match min { /*@*/ @@ -47,7 +47,7 @@ fn vec_min(v: &Vec) -> Option { //@ the intermediate variable `min`, which also has type `Option`. If you replace get rid of the //@ `e.clone()`, Rust will complain "Cannot move out of borrowed content". That's because //@ `e` is a `&BigInt`. Assigning `min = Some(*e)` works just like a function call: Ownership of the -//@ underlying data is transferred from where `e` borrows from to `min`. But that's not allowed, since +//@ underlying data is transferred from `e` to `min`. But that's not allowed, since //@ we just borrowed `e`, so we cannot empty it! We can, however, call `clone` on it. Then we own //@ the copy that was created, and hence we can store it in `min`.
//@ Of course, making such a full copy is expensive, so we'd like to avoid it. We'll come to that in the next part. @@ -94,10 +94,11 @@ impl Copy for SomethingOrNothing {} // ## Lifetimes //@ To fix the performance problems of `vec_min`, we need to avoid using `clone`. We'd like //@ the return value to not be owned (remember that this was the source of our need for cloning), but *borrowed*. +//@ In other words, we want to return a shared reference to the minimal element. -//@ The function `head` demonstrates how that could work: It borrows the first element of a vector if it is non-empty. +//@ The function `head` demonstrates how that could work: It returns a reference to the first element of a vector if it is non-empty. //@ The type of the function says that it will either return nothing, or it will return a borrowed `T`. -//@ We can then borrow the first element of `v` and use it to construct the return value. +//@ We can then obtain a reference to the first element of `v` and use it to construct the return value. fn head(v: &Vec) -> Option<&T> { if v.len() > 0 { Some(&v[0]) /*@*/ @@ -126,25 +127,25 @@ fn rust_foo(mut v: Vec) -> i32 { *first.unwrap() } -//@ To give the answer to this question, we have to talk about the *lifetime* of a borrow. The point is, saying that +//@ To give the answer to this question, we have to talk about the *lifetime* of a reference. The point is, saying that //@ you borrowed your friend a `Vec`, or a book, is not good enough, unless you also agree on *how long* //@ your friend can borrow it. After all, you need to know when you can rely on owning your data (or book) again. //@ -//@ Every borrow in Rust has an associated lifetime, written `&'a T` for a borrow of type `T` with lifetime `'a`. The full +//@ Every reference in Rust has an associated lifetime, written `&'a T` for a reference with lifetime `'a` to something of type `T`. The full //@ type of `head` reads as follows: `fn<'a, T>(&'a Vec) -> Option<&'a T>`. Here, `'a` is a *lifetime variable*, which -//@ represents how long the vector has been borrowed. The function type expresses that argument and return value have *the same lifetime*. +//@ represents for how long the vector has been borrowed. The function type expresses that argument and return value have *the same lifetime*. //@ //@ When analyzing the code of `rust_foo`, Rust has to assign a lifetime to `first`. It will choose the scope //@ where `first` is valid, which is the entire rest of the function. Because `head` ties the lifetime of its //@ argument and return value together, this means that `&v` also has to borrow `v` for the entire duration of -//@ the function `rust_foo`. So when we try to borrow `v` as mutable for `push`, Rust complains that the two borrows (the one +//@ the function `rust_foo`. So when we try to borrow `v` exclusively for `push`, Rust complains that the two references (the one //@ for `head`, and the one for `push`) overlap. Lucky us! Rust caught our mistake and made sure we don't crash the program. //@ -//@ So, to sum this up: Lifetimes enable Rust to reason about *how long* a pointer has been borrowed. We can thus -//@ safely write functions like `head`, that return pointers into data they got as argument, and make sure they +//@ So, to sum this up: Lifetimes enable Rust to reason about *how long* a reference is valid, how long ownership has been borrowed. We can thus +//@ safely write functions like `head`, that return references into data they got as argument, and make sure they //@ are used correctly, *while looking only at the function type*. At no point in our analysis of `rust_foo` did //@ we have to look *into* `head`. That's, of course, crucial if we want to separate library code from application code. -//@ Most of the time, we don't have to explicitly add lifetimes to function types. This is thanks to *lifetimes elision*, +//@ Most of the time, we don't have to explicitly add lifetimes to function types. This is thanks to *lifetime elision*, //@ where Rust will automatically insert lifetimes we did not specify, following some [simple, well-documented rules](https://doc.rust-lang.org/stable/book/lifetimes.html#lifetime-elision). //@ [index](main.html) | [previous](part05.html) | [raw source](https://www.ralfj.de/git/rust-101.git/blob_plain/HEAD:/workspace/src/part06.rs) | [next](part07.html) diff --git a/src/part07.rs b/src/part07.rs index 85218bc..2d88390 100644 --- a/src/part07.rs +++ b/src/part07.rs @@ -4,8 +4,8 @@ pub use part05::BigInt; // With our new knowledge of lifetimes, we are now able to write down the desired type of `min`: -//@ We want the function to take two borrows *of the same lifetime*, and then -//@ return a borrow of that lifetime. If the two input lifetimes would be different, we +//@ We want the function to take two references *with the same lifetime*, and then +//@ return a reference with that lifetime. If the two input lifetimes would be different, we //@ would not know which lifetime to use for the result. pub trait Minimum { fn min<'a>(&'a self, other: &'a Self) -> &'a Self; diff --git a/src/part08.rs b/src/part08.rs index 2c6dfc5..340de24 100644 --- a/src/part08.rs +++ b/src/part08.rs @@ -91,13 +91,13 @@ impl ops::Add for BigInt { } } -// ## Traits and borrowed types +// ## Traits and reference types //@ If you inspect the addition function above closely, you will notice that it actually consumes ownership of both operands //@ to produce the result. This is, of course, in general not what we want. We'd rather like to be able to add two `&BigInt`. // Writing this out becomes a bit tedious, because trait implementations (unlike functions) require full explicit annotation // of lifetimes. Make sure you understand exactly what the following definition says. Notice that we can implement a trait for -// a borrowed type! +// a reference type! impl<'a, 'b> ops::Add<&'a BigInt> for &'b BigInt { type Output = BigInt; fn add(self, rhs: &'a BigInt) -> Self::Output { diff --git a/src/part09.rs b/src/part09.rs index d4b3e66..ecb9ffa 100644 --- a/src/part09.rs +++ b/src/part09.rs @@ -14,9 +14,9 @@ use part05::BigInt; //@ digit comes first. So, we have to write down some type, and implement `Iterator` for it such that `next` returns the digits //@ one-by-one. Clearly, the iterator must somehow be able to access the number it iterates over, and it must store its current //@ location. However, it cannot *own* the `BigInt`, because then the number would be gone after iteration! That'd certainly be bad. -//@ The only alternative is for the iterator to *borrow* the number. +//@ The only alternative is for the iterator to *borrow* the number, so it takes a reference. -//@ In writing this down, we again have to be explicit about the lifetime of the borrow: We can't just have an +//@ In writing this down, we again have to be explicit about the lifetime of the reference: We can't just have an //@ `Iter`, we must have an `Iter<'a>` that borrows the number for lifetime `'a`. This is our first example of //@ a data-type that's polymorphic in a lifetime, as opposed to a type.
//@ `usize` here is the type of unsigned, pointer-sized numbers. It is typically the type of "lengths of things", @@ -47,7 +47,7 @@ impl<'a> Iterator for Iter<'a> { // All we need now is a function that creates such an iterator for a given `BigInt`. impl BigInt { //@ Notice that when we write the type of `iter`, we don't actually have to give the lifetime parameter of `Iter`. Just as it is - //@ the case with functions returning borrowed data, you can elide the lifetime. The rules for adding the lifetimes are exactly the + //@ the case with functions returning references, you can elide the lifetime. The rules for adding the lifetimes are exactly the //@ same. (See the last section of [part 06](part06.html).) fn iter(&self) -> Iter { Iter { num: self, idx: self.data.len() } /*@*/ @@ -94,7 +94,7 @@ fn print_digits_v2(b: &BigInt) { // ## Iterator invalidation and lifetimes //@ You may have been surprised that we had to explicitly annotate a lifetime when we wrote `Iter`. Of -//@ course, with lifetimes being present at every borrow in Rust, this is only consistent. But do we at +//@ course, with lifetimes being present at every reference in Rust, this is only consistent. But do we at //@ least gain something from this extra annotation burden? (Thankfully, this burden only occurs when we //@ define *types*, and not when we define functions - which is typically much more common.) @@ -130,7 +130,7 @@ fn iter_invalidation_demo() { //@ of the right type, the conversion function will not do anything and trivially be optimized away. //@ If you have a look at the documentation of `IntoIterator`, you will notice that the function `into_iter` it provides actually -//@ consumes its argument. So we implement the trait for *borrowed* numbers, such that the number is not lost after the iteration. +//@ consumes its argument. So we implement the trait for *references to* numbers, such that the number is not lost after the iteration. impl<'a> IntoIterator for &'a BigInt { type Item = u64; type IntoIter = Iter<'a>; @@ -140,7 +140,7 @@ impl<'a> IntoIterator for &'a BigInt { } // With this in place, you can now replace `b.iter()` in `main` by `&b`. Go ahead and try it!
//@ Wait, `&b`? Why that? Well, we implemented `IntoIterator` for `&BigInt`. If we are in a place where `b` is already borrowed, we can -//@ just do `for digit in b`. If however, we own `b`, we have to borrow it. Alternatively, we could implement `IntoIterator` +//@ just do `for digit in b`. If however, we own `b`, we have to create a reference to it. Alternatively, we could implement `IntoIterator` //@ for `BigInt` - which, as already mentioned, would mean that `b` is actually consumed by the iteration, and gone. This can easily happen, //@ for example, with a `Vec`: Both `Vec` and `&Vec` (and `&mut Vec`) implement `IntoIterator`, so if you do `for e in v`, and `v` has type `Vec`, //@ then you will obtain ownership of the elements during the iteration - and destroy the vector in the process. We actually did that in diff --git a/src/part10.rs b/src/part10.rs index 6969889..b586cd5 100644 --- a/src/part10.rs +++ b/src/part10.rs @@ -9,10 +9,10 @@ use part05::BigInt; //@ our function. In Rust, a natural first attempt to express this is to have a trait for it. // So, let us define a trait that demands that the type provides some method `do_action` on digits. -//@ This immediately raises the question: How do we pass `self` to that function? Owned, shared borrow, -//@ or mutable borrow? The typical strategy to answer this question is to use the strongest +//@ This immediately raises the question: How do we pass `self` to that function? Owned, shared reference, +//@ or mutable reference? The typical strategy to answer this question is to use the strongest //@ type that still works. Certainly, passing `self` in owned form does not work: Then the function -//@ would consume `self`, and we could not call it again, on the second digit. So let's go with a mutable borrow. +//@ would consume `self`, and we could not call it again, on the second digit. So let's go with a mutable reference. trait Action { fn do_action(&mut self, digit: u64); } @@ -21,7 +21,7 @@ trait Action { impl BigInt { fn act_v1(&self, mut a: A) { //@ Remember that the `mut` above is just an annotation to Rust, telling it that we're okay with `a` being mutated. - //@ Calling `do_action` on `a` takes a mutable borrow, so mutation could indeed happen. + //@ Calling `do_action` on `a` takes a mutable reference, so mutation could indeed happen. for digit in self { a.do_action(digit); /*@*/ } @@ -62,7 +62,7 @@ pub fn main() { //@ In general, this is called a *closure*. Closures take some arguments and produce a result, and they have an *environment* //@ they can use, which corresponds to the type `PrintWithString` (or any other type implementing `Action`). Again we have the //@ choice of passing this environment in owned or borrowed form, so there are three traits for closures in Rust: `Fn`-closures -//@ get a shared borrow, `FnMut`-closures get a mutable borrow, and `FnOnce`-closures consume their environment (and can hence +//@ get a shared reference, `FnMut`-closures get a mutable reference, and `FnOnce`-closures consume their environment (and can hence //@ be called only once). The syntax for a closure trait which takes arguments of type `T1`, `T2`, ... and returns something //@ of type `U` is `Fn(T1, T2, ...) -> U`. @@ -94,11 +94,11 @@ pub fn print_with_prefix(b: &BigInt, prefix: String) { // For example, we can use that to count the digits as they are printed. pub fn print_and_count(b: &BigInt) { let mut count: usize = 0; - //@ This time, the environment will contain a field of type `&mut usize`, that will be initialized with a mutable borrow of + //@ This time, the environment will contain a field of type `&mut usize`, that will be initialized with a mutable reference of //@ `count`. The closure, since it mutably borrows its environment, is able to access this field and mutate `count` - //@ through it. Once `act` returns, the closure is destroyed and the borrow of `count` ends. Because closures compile down + //@ through it. Once `act` returns, the closure is destroyed and `count` is no longer borrowed. Because closures compile down //@ to normal types, all the borrow checking continues to work as usually, and we cannot accidentally leak a closure somewhere - //@ that still contains, in its environment, a borrow that has ended. + //@ that still contains, in its environment, a dead reference. b.act(|digit| { println!("{}: {}", count, digit); count = count +1; } ); println!("There are {} digits", count); } diff --git a/src/part11.rs b/src/part11.rs index 9217be8..a5cafa1 100644 --- a/src/part11.rs +++ b/src/part11.rs @@ -48,7 +48,7 @@ impl Callbacks { // We can also write a generic version of `register`, such that it will be instantiated with some concrete closure type `F` // and do the creation of the `Box` and the conversion from `F` to `FnMut(i32)` itself. - //@ For this to work, we need to demand that the type `F` does not contain any short-lived borrows. After all, we will store it + //@ For this to work, we need to demand that the type `F` does not contain any short-lived references. After all, we will store it //@ in our list of callbacks indefinitely. If the closure contained a pointer to our caller's stackframe, that pointer //@ could be invalid by the time the closure is called. We can mitigate this by bounding `F` by a *lifetime*: `F: 'a` says //@ that all data of type `F` will *outlive* (i.e., will be valid for at least as long as) lifetime `'a`. @@ -64,13 +64,13 @@ impl Callbacks { // Since they are of type `FnMut`, we need to mutably iterate. for callback in self.callbacks.iter_mut() { //@ Here, `callback` has type `&mut Box`. We can make use of the fact that `Box` is a *smart pointer*: In - //@ particular, we can use it as if it were a normal pointer, and use `*` to get to its contents. Then we mutably borrow - //@ these contents, because we call a `FnMut`. + //@ particular, we can use it as if it were a normal reference, and use `*` to get to its contents. Then we obtain a + //@ mutable reference to these contents, because we call a `FnMut`. (&mut *callback)(val); /*@*/ - //@ Just like it is the case with normal borrows, this typically happens implicitly, so we can also directly call the function. + //@ Just like it is the case with normal references, this typically happens implicitly with smart pointers, so we can also directly call the function. //@ Try removing the `&mut *`. //@ - //@ The difference to a normal pointer is that `Box` implies ownership: Once you drop the box (i.e., when the entire `Callbacks` instance is + //@ The difference to a reference is that `Box` implies full ownership: Once you drop the box (i.e., when the entire `Callbacks` instance is //@ dropped), the content it points to on the heap will be deleted. } } @@ -83,9 +83,9 @@ pub fn main() { c.call(0); { - //@ We can even register callbacks that modify their environment. Per default, Rust will attempt to borrow `count`. However, + //@ We can even register callbacks that modify their environment. Per default, Rust will attempt to capture a reference to `count`, to borrow it. However, //@ that doesn't work out this time. Remember the `'static` bound above? Borrowing `count` in the environment would - //@ violate that bound, as the borrow is only valid for this block. If the callbacks are triggered later, we'd be in trouble. + //@ violate that bound, as the reference is only valid for this block. If the callbacks are triggered later, we'd be in trouble. //@ We have to explicitly tell Rust to `move` ownership of the variable into the closure. Its environment will then contain a //@ `usize` rather than a `&mut usize`, and the closure has no effect on this local variable anymore. let mut count: usize = 0; @@ -99,7 +99,7 @@ pub fn main() { //@ ## Run-time behavior //@ When you run the program above, how does Rust know what to do with the callbacks? Since an unsized type lacks some information, -//@ a *pointer* to such a type (be it a `Box` or a borrow) will need to complete this information. We say that pointers to +//@ a *pointer* to such a type (be it a `Box` or a reference) will need to complete this information. We say that pointers to //@ trait objects are *fat*. They store not only the address of the object, but (in the case of trait objects) also a *vtable*: A //@ table of function pointers, determining the code that's run when a trait method is called. There are some restrictions for traits to be usable //@ as trait objects. This is called *object safety* and described in [the documentation](https://doc.rust-lang.org/stable/book/trait-objects.html) and [the reference](https://doc.rust-lang.org/reference.html#trait-objects). @@ -113,8 +113,8 @@ pub fn main() { //@ (Of course, in the case of `register` above, there's no function called on the trait object.) //@ Isn't it beautiful how traits can nicely handle this tradeoff (and much more, as we saw, like closures and operator overloading)? -// **Exercise 11.1**: We made the arbitrary choice of using `i32` for the arguments. Generalize the data-structures above +// **Exercise 11.1**: We made the arbitrary choice of using `i32` for the arguments. Generalize the data structures above // to work with an arbitrary type `T` that's passed to the callbacks. Since you need to call multiple callbacks with the -// same `t: T`, you will either have to restrict `T` to `Copy` types, or pass a borrow. +// same `t: T`, you will either have to restrict `T` to `Copy` types, or pass a reference. //@ [index](main.html) | [previous](part10.html) | [raw source](https://www.ralfj.de/git/rust-101.git/blob_plain/HEAD:/workspace/src/part11.rs) | [next](part12.html) diff --git a/src/part12.rs b/src/part12.rs index d88a8a6..8b04fb6 100644 --- a/src/part12.rs +++ b/src/part12.rs @@ -16,10 +16,10 @@ use std::cell::{Cell, RefCell}; //@ references are gone, the data is deleted. //@ //@ Wait a moment, you may say here. Multiple references to the same data? That's aliasing! Indeed: -//@ Once data is stored in an `Rc`, it is read-only and you can only ever get a shared borrow of the data again. +//@ Once data is stored in an `Rc`, it is read-only and you can only ever get a shared reference to the data again. -//@ Because of this read-only restriction, we cannot use `FnMut` here: We'd be unable to call the function with a mutable borrow -//@ of it's environment! So we have to go with `Fn`. We wrap that in an `Rc`, and then Rust happily derives `Clone` for us. +//@ Because of this read-only restriction, we cannot use `FnMut` here: We'd be unable to call the function with a mutable reference +//@ to it's environment! So we have to go with `Fn`. We wrap that in an `Rc`, and then Rust happily derives `Clone` for us. #[derive(Clone)] struct Callbacks { callbacks: Vec>, @@ -61,8 +61,8 @@ pub fn main() { //@ So it would be rather sad if we were not able to write this program. Lucky enough, Rust's standard library provides a //@ solution in the form of `Cell`. This type represents a memory cell of some type `T`, providing the two basic operations //@ `get` and `set`. `get` returns a *copy* of the content of the cell, so all this works only if `T` is `Copy`. -//@ `set`, which overrides the content, only needs a *shared borrow* of the cell. The phenomenon of a type that permits mutation through -//@ shared borrows (i.e., mutation despite the possibility of aliasing) is called *interior mutability*. You can think +//@ `set`, which overrides the content, only needs a *shared reference* to the cell. The phenomenon of a type that permits mutation through +//@ shared references (i.e., mutation despite the possibility of aliasing) is called *interior mutability*. You can think //@ of `set` changing only the *contents* of the cell, not its *identity*. In contrast, the kind of mutation we saw so far was //@ about replacing one piece of data by something else of the same type. This is called *inherited mutability*.
//@ Notice that it is impossible to *borrow* the contents of the cell, and that is actually the key to why this is safe. @@ -73,7 +73,7 @@ fn demo_cell(c: &mut Callbacks) { let count = Cell::new(0); // Again, we have to move ownership if the `count` into the environment closure. c.register(move |val| { - // In here, all we have is a shared borrow of our environment. But that's good enough for the `get` and `set` of the cell! + // In here, all we have is a shared reference of our environment. But that's good enough for the `get` and `set` of the cell! //@ At run-time, the `Cell` will be almost entirely compiled away, so this becomes pretty much equivalent to the version //@ we wrote in the previous part. let new_count = count.get()+1; @@ -86,8 +86,13 @@ fn demo_cell(c: &mut Callbacks) { } //@ It is worth mentioning that `Rc` itself also has to make use of interior mutability: When you `clone` an `Rc`, all it has available -//@ is a shared borrow. However, it has to increment the reference count! Internally, `Rc` uses `Cell` for the count, such that it +//@ is a shared reference. However, it has to increment the reference count! Internally, `Rc` uses `Cell` for the count, such that it //@ can be updated during `clone`. +//@ +//@ Putting it all together, the story around mutation and ownership through references looks as follows: There are *exclusive* references, +//@ which - because of their exclusivity - are always safe to mutate through. And there are *shared* references, where the compiler cannot +//@ generally promise that mutation is safe. However, if extra circumstances guarantee that mutation *is* safe, then it can happen even +//@ through a sahred reference - as we saw with `Cell`. // ## `RefCell` //@ As the next step in the evolution of `Callbacks`, we could try to solve this problem of mutability once and for all, by adding `Cell` @@ -120,23 +125,23 @@ impl CallbacksMut { pub fn call(&mut self, val: i32) { for callback in self.callbacks.iter() { // We have to *explicitly* borrow the contents of a `RefCell` by calling `borrow` or `borrow_mut`. - //@ At run-time, the cell will keep track of the number of outstanding shared and mutable borrows, + //@ At run-time, the cell will keep track of the number of outstanding shared and mutable references, //@ and panic if the rules are violated.
- //@ For this check to be performed, `closure` is a *guard*: Rather than a normal borrow, `borrow_mut` returns - //@ a smart pointer (`RefMut`, in this case) that waits until is goes out of scope, and then - //@ appropriately updates the number of active borrows. + //@ For this check to be performed, `closure` is a *guard*: Rather than a normal reference, `borrow_mut` returns + //@ a smart pointer ([`RefMut`](https://doc.rust-lang.org/stable/std/cell/struct.RefMut.html), in this case) that waits until is goes out of scope, and then + //@ appropriately updates the number of active references. //@ //@ Since `call` is the only place that borrows the environments of the closures, we should expect that - //@ the check will always succeed. However, this is not actually true. Several different `CallbacksMut` could share + //@ the check will always succeed, as is actually entirely useless. However, this is not actually true. Several different `CallbacksMut` could share //@ a callback (as they were created with `clone`), and calling one callback here could trigger calling - //@ all callbacks of the other `CallbacksMut`, which would end up calling the initial callback again. This issue is called *reentrancy*, - //@ and it can lead to subtle bugs. Here, it would mean that the closure runs twice, each time thinking it has the only - //@ mutable borrow of its environment - so it may end up dereferencing a dangling pointer. Ouch! Lucky enough, + //@ all callbacks of the other `CallbacksMut`, which would end up calling the initial callback again. This issue of functions accidentally recursively calling + //@ themselves is called *reentrancy*, and it can lead to subtle bugs. Here, it would mean that the closure runs twice, each time thinking it has an + //@ exclusive, mutable reference to its environment - so it may end up dereferencing a dangling pointer. Ouch! Lucky enough, //@ Rust detects this at run-time and panics once we try to borrow the same environment again. I hope this also makes it //@ clear that there's absolutely no hope of Rust performing these checks statically, at compile-time: It would have to detect reentrancy! let mut closure = callback.borrow_mut(); // Unfortunately, Rust's auto-dereference of pointers is not clever enough here. We thus have to explicitly - // dereference the smart pointer and obtain a mutable borrow of the content. + // dereference the smart pointer and obtain a mutable reference to the content. (&mut *closure)(val); } } @@ -159,6 +164,6 @@ fn demo_mut(c: &mut CallbacksMut) { } // **Exercise 12.1**: Write some piece of code using only the available, public interface of `CallbacksMut` such that a reentrant call to a closure -// is happening, and the program aborts because the `RefCell` refuses to hand out a second mutable borrow of the closure's environment. +// is happening, and the program panics because the `RefCell` refuses to hand out a second mutable borrow of the closure's environment. //@ [index](main.html) | [previous](part11.html) | [raw source](https://www.ralfj.de/git/rust-101.git/blob_plain/HEAD:/workspace/src/part12.rs) | [next](part13.html) diff --git a/src/part13.rs b/src/part13.rs index 7aedb71..4fbc908 100644 --- a/src/part13.rs +++ b/src/part13.rs @@ -159,8 +159,8 @@ pub fn main() { //@ programs memory safe, and that prevents us from invalidating iterators, also helps secure our multi-threaded code against //@ data races. For example, notice how `read_files` sends a `String` to `filter_lines`. At run-time, only the pointer to //@ the character data will actually be moved around (just like when a `String` is passed to a function with full ownership). However, -//@ `read_files` has to *give up* ownership of the string to perform `send`, to it is impossible for an outstanding borrow to -//@ still be around. After it sent the string to the other side, `read_files` has no pointer into the string content +//@ `read_files` has to *give up* ownership of the string to perform `send`, to it is impossible for the string to still be borrowed. +//@ After it sent the string to the other side, `read_files` has no pointer into the string content //@ anymore, and hence no way to race on the data with someone else. //@ //@ There is a little more to this. Remember the `'static` bound we had to add to `register` in the previous parts, to make diff --git a/src/part14.rs b/src/part14.rs index 45044af..ae13e01 100644 --- a/src/part14.rs +++ b/src/part14.rs @@ -15,8 +15,8 @@ //@ `[T]` is the type of an (unsized) *array*, with elements of type `T`. All this means is that there's a contiguous //@ region of memory, where a bunch of `T` are stored. How many? We can't tell! This is an unsized type. Just like for //@ trait objects, this means we can only operate on pointers to that type, and these pointers will carry the missing -//@ information - namely, the length. Such a pointer is called a *slice*. As we will see, a slice can be split. -//@ Our function can thus take a borrowed slice, and promise to sort all elements in there. +//@ information - namely, the length (they will be *fat pointers*). Such a reference to an array is called a *slice*. As we will see, a slice can be split. +//@ Our function can thus take a mutable slice, and promise to sort all elements in there. pub fn sort(data: &mut [T]) { if data.len() < 2 { return; } @@ -38,7 +38,7 @@ pub fn sort(data: &mut [T]) { // Finally, we split our slice to sort the two halves. The nice part about slices is that splitting them is cheap: //@ They are just a pointer to a start address, and a length. We can thus get two pointers, one at the beginning and //@ one in the middle, and set the lengths appropriately such that they don't overlap. This is what `split_at_mut` does. - //@ Since the two slices don't overlap, there is no aliasing and we can have them both mutably borrowed. + //@ Since the two slices don't overlap, there is no aliasing and we can have both of them as exclusive, mutable slices. let (part1, part2) = data.split_at_mut(lpos); //@ The index operation can not only be used to address certain elements, it can also be used for *slicing*: Giving a range //@ of indices, and obtaining an appropriate part of the slice we started with. Here, we remove the last element from @@ -131,7 +131,7 @@ Options: //@ encoded string, that is, a bunch of bytes in memory (`[u8]`) that are valid according of UTF-8. `str` is unsized. `&str` //@ stores the address of the character data, and their length. String literals like "this one" are //@ of type `&'static str`: They point right to the constant section of the binary, so - //@ the borrow is valid for the entire program. The bytes pointed to by `pattern`, on the other hand, are owned by someone else, + //@ the reference is valid for the entire program. The bytes pointed to by `pattern`, on the other hand, are owned by someone else, //@ and we call `to_string` on it to copy the string data into a buffer on the heap that we own. let mode = if count { OutputMode::Count diff --git a/src/part15.rs b/src/part15.rs index 3b59825..47952d6 100644 --- a/src/part15.rs +++ b/src/part15.rs @@ -45,7 +45,7 @@ impl ConcurrentCounter { *counter = *counter + by; //@ At the end of the function, `counter` is dropped and the mutex is available again. //@ This can only happen when full ownership of the guard is given up. In particular, it is impossible for us - //@ to borrow some of its content, release the lock of the mutex, and subsequently access the protected data without holding + //@ to take a reference to some of its content, release the lock of the mutex, and subsequently access the protected data without holding //@ the lock. Enforcing the locking discipline is expressible in the Rust type system, so we don't have to worry //@ about data races *even though* we are mutating shared memory! //@ @@ -105,7 +105,7 @@ pub fn main() { //@ ## `RwLock` //@ Besides `Mutex`, there's also [`RwLock`](https://doc.rust-lang.org/stable/std/sync/struct.RwLock.html), which //@ provides two ways of locking: One that grants only read-only access, to any number of concurrent readers, and another one -//@ for exclusive write access. Notice that this is the same pattern we already saw with shared vs. mutable borrows. Hence +//@ for exclusive write access. Notice that this is the same pattern we already saw with shared vs. mutable references. Hence //@ another way of explaining `RwLock` is to say that it is like `RefCell`, but works even for concurrent access. Rather than //@ panicking when the data is already borrowed, `RwLock` will of course block the current thread until the lock is available. //@ In this view, `Mutex` is a stripped-down version of `RwLock` that does not distinguish readers and writers. @@ -118,26 +118,26 @@ pub fn main() { //@ `RefCell` across multiple threads? //@ //@ In part 13, we talked about types that are marked `Send` and thus can be moved to another thread. However, we did *not* -//@ talk about the question whether a borrow is `Send`. For `&mut T`, the answer is: It is `Send` whenever `T` is send. +//@ talk about the question whether a reference is `Send`. For `&mut T`, the answer is: It is `Send` whenever `T` is send. //@ `&mut` allows moving values back and forth, it is even possible to [`swap`](https://doc.rust-lang.org/stable/std/mem/fn.swap.html) -//@ the contents of two mutably borrowed values. So in terms of concurrency, sending a mutable borrow is very much like +//@ the contents of two mutable references. So in terms of concurrency, sending a mutable, exclusive reference is very much like //@ sending full ownership, in the sense that it can be used to move the object to another thread. //@ -//@ But what about `&T`, a shared borrow? Without interior mutability, it would always be all-right to send such values. +//@ But what about `&T`, a shared reference? Without interior mutability, it would always be all-right to send such values. //@ After all, no mutation can be performed, so there can be as many threads accessing the data as we like. In the //@ presence of interior mutability though, the story gets more complicated. Rust introduces another marker trait for //@ this purpose: `Sync`. A type `T` is `Sync` if and only if `&T` is `Send`. Just like `Send`, `Sync` has a default implementation //@ and is thus automatically implemented for a data-structure *if* all its members implement it. //@ -//@ Since `Arc` provides multiple threads with a shared borrow of its content, `Arc` is only `Send` if `T` is `Sync`. +//@ Since `Arc` provides multiple threads with a shared reference to its content, `Arc` is only `Send` if `T` is `Sync`. //@ So if we had used `RefCell` above, which is *not* `Sync`, Rust would have caught that mistake. Notice however that //@ `RefCell` *is* `Send`: If ownership of the entire cell is moved to another thread, it is still not possible for several //@ threads to try to access the data at the same time. //@ -//@ Almost all the types we saw so far are `Sync`, with the exception of `Rc`. Remember that a shared borrow is good enough -//@ for cloning, and we don't want other threads to clone our local `Rc`, so it must not be `Sync`. The rule of `Mutex` -//@ is to enforce synchronization, so it should not be entirely surprising that `Mutex` is `Send` *and* `Sync` provided that -//@ `T` is `Send`. +//@ Almost all the types we saw so far are `Sync`, with the exception of `Rc`. Remember that a shared reference is good enough +//@ for cloning, and we don't want other threads to clone our local `Rc` (they would race for updating the reference count), +//@ so it must not be `Sync`. The rule of `Mutex` is to enforce synchronization, so it should not be entirely surprising that +//@ `Mutex` is `Send` *and* `Sync` provided that `T` is `Send`. //@ //@ You may be curious whether there is a type that's `Sync`, but not `Send`. There are indeed rather esoteric examples //@ of such types, but that's not a topic I want to go into. In case you are curious, there's a diff --git a/src/part16.rs b/src/part16.rs index 90b687b..748e7d9 100644 --- a/src/part16.rs +++ b/src/part16.rs @@ -32,7 +32,7 @@ struct Node { data: T, } // A node pointer is a *mutable raw pointer* to a node. -//@ Raw pointers (`*mut T` and `*const T`) are the Rust equivalent of pointers in C. Unlike borrows, they do not come with +//@ Raw pointers (`*mut T` and `*const T`) are the Rust equivalent of pointers in C. Unlike references, they do not come with //@ any guarantees: Raw pointers can be null, or they can point to garbage. They don't have a lifetime, either. type NodePtr = *mut Node; @@ -115,7 +115,7 @@ impl LinkedList { //@ that it is going to visit. However, how do we make sure that this pointer remains valid? We have to //@ get this right ourselves, as we left the safe realms of borrowing and ownership. Remember that the //@ key ingredient for iterator safety was to tie the lifetime of the iterator to the lifetime of the -//@ borrow used for `iter_mut`. We will thus give `IterMut` two parameters: A type parameter specifying +//@ reference used for `iter_mut`. We will thus give `IterMut` two parameters: A type parameter specifying //@ the type of the data, and a lifetime parameter specifying for how long the list was borrowed to the //@ iterator. @@ -123,7 +123,7 @@ impl LinkedList { //@ the data in the list lives at least as long as the iterator: If you drop the `T: 'a`, Rust will tell //@ you to add it back. And secondly, Rust will complain if `'a` is not actually used in the struct. //@ It doesn't know what it is supposed to do with that lifetime. So we use `PhantomData` again to tell -//@ it that in terms of ownership, this type actually (mutably) borrows a linked list. This has no +//@ it that in terms of ownership, this type actually (exclusively) borrows a linked list. This has no //@ operational effect, but it means that Rust can deduce the intent we had when adding that //@ seemingly useless lifetime parameter. pub struct IterMut<'a, T> where T: 'a { @@ -141,7 +141,7 @@ impl<'a, T> Iterator for IterMut<'a, T> { if self.next.is_null() { None } else { - // Otherwise, we can convert the next pointer to a borrow, get a borrow to the data + // Otherwise, we can convert the next pointer to a reference, get a reference to the data // and update the iterator. let next = unsafe { &mut *self.next }; let ret = &mut next.data; @@ -154,12 +154,12 @@ impl<'a, T> Iterator for IterMut<'a, T> { //@ In `next` above, we made crucial use of the assumption that `self.next` is either null or a valid pointer. //@ This only works because if someone tries to delete elements from a list during iteration, we know that the borrow checker //@ will catch them: If they call `next`, the lifetime `'a` we artificially added to the iterator has to still be -//@ active, which means the mutable borrow passed to `iter_mut` is still active, which means nobody can delete +//@ active, which means the mutable reference passed to `iter_mut` is still active, which means nobody can delete //@ anything from the list. In other words, we make use of the expressive type system of Rust, decorating //@ our own unsafe implementation with just enough information so that Rust can check *uses* of the linked-list. //@ If the type system were weaker, we could not write a linked-list like the above with a safe interface! -// **Exercise 16.2**: Add a method `iter` and a type `Iter` providing iteration for shared borrows. +// **Exercise 16.2**: Add a method `iter` and a type `Iter` providing iteration for shared references. // Add testcases for both kinds of iterators. // ## `Drop` @@ -173,7 +173,7 @@ impl<'a, T> Iterator for IterMut<'a, T> { //@ of `LinkedList`. impl Drop for LinkedList { // The destructor itself is a method which takes `self` in mutably borrowed form. It cannot own `self`, because then - // the destructor of `self` would be called at the end of the function, resulting in endless recursion... + // the destructor of `self` would be called at the end of the function, resulting in endless recursion. fn drop(&mut self) { let mut cur_ptr = self.first; while !cur_ptr.is_null() { diff --git a/workspace/src/part04.rs b/workspace/src/part04.rs index bde913f..2a3a648 100644 --- a/workspace/src/part04.rs +++ b/workspace/src/part04.rs @@ -1,5 +1,5 @@ -// Rust-101, Part 04: Ownership, Borrowing -// ======================================= +// Rust-101, Part 04: Ownership, Borrowing, References +// =================================================== /* void foo(std::vector v) { @@ -17,14 +17,14 @@ fn ownership_demo() { /* println!("The first element is: {}", v[0]); */ /* BAD! */ } -// ## Shared borrowing +// ## Borrowing a shared reference fn vec_min(v: &Vec) -> Option { use std::cmp; let mut min = None; - // This time, we explicitly request an iterator for the vector `v`. The method `iter` borrows the vector - // it works on, and provides shared borrows of the elements. + // This time, we explicitly request an iterator for the vector `v`. The method `iter` just borrows the vector + // it works on, and provides shared references to the elements. for e in v.iter() { // In the loop, `e` now has type `&i32`, so we have to dereference it to obtain an `i32`. min = Some(match min { @@ -36,7 +36,7 @@ fn vec_min(v: &Vec) -> Option { } // Now that `vec_min` does not acquire ownership of the vector anymore, we can call it multiple times on the same vector and also do things like -fn shared_borrow_demo() { +fn shared_ref_demo() { let v = vec![5,4,3,2,1]; let first = &v[0]; vec_min(&v); @@ -44,7 +44,7 @@ fn shared_borrow_demo() { println!("The first element is: {}", *first); } -// ## Mutable borrowing +// ## Exclusive, mutable references fn vec_inc(v: &mut Vec) { for e in v.iter_mut() { @@ -52,7 +52,7 @@ fn vec_inc(v: &mut Vec) { } } // Here's an example of calling `vec_inc`. -fn mutable_borrow_demo() { +fn mutable_ref_demo() { let mut v = vec![5,4,3,2,1]; /* let first = &v[0]; */ vec_inc(&mut v); @@ -64,8 +64,8 @@ fn mutable_borrow_demo() { // The ownership and borrowing system of Rust enforces the following three rules: // // * There is always exactly one owner of a piece of data -// * If there is an active mutable borrow, then nobody else can have active access to the data -// * If there is an active shared borrow, then every other active access to the data is also a shared borrow +// * If there is an active mutable reference, then nobody else can have active access to the data +// * If there is an active shared reference, then every other active access to the data is also a shared reference // // As it turns out, combined with the abstraction facilities of Rust, this is a very powerful mechanism // to tackle many problems beyond basic memory safety. You will see some examples for this soon. diff --git a/workspace/src/part05.rs b/workspace/src/part05.rs index 65f6f7d..28c4030 100644 --- a/workspace/src/part05.rs +++ b/workspace/src/part05.rs @@ -61,7 +61,7 @@ impl Clone for SomethingOrNothing { } // **Exercise 05.2**: Write some more functions on `BigInt`. What about a function that returns the number of -// digits? The number of non-zero digits? The smallest/largest digit? Of course, these should all just borrow `self`. +// digits? The number of non-zero digits? The smallest/largest digit? Of course, these should all take `self` as a shared reference (i.e., in borrowed form). // ## Mutation + aliasing considered harmful (part 2) enum Variant { diff --git a/workspace/src/part06.rs b/workspace/src/part06.rs index 04a0847..3bc7d08 100644 --- a/workspace/src/part06.rs +++ b/workspace/src/part06.rs @@ -25,7 +25,7 @@ impl BigInt { // Now we can write `vec_min`. fn vec_min(v: &Vec) -> Option { let mut min: Option = None; - // If `v` is a shared borrowed vector, then the default for iterating over it is to call `iter`, the iterator that borrows the elements. + // If `v` is a shared reference to a vector, then the default for iterating over it is to call `iter`, the iterator that borrows the elements. for e in v { let e = e.clone(); unimplemented!() diff --git a/workspace/src/part08.rs b/workspace/src/part08.rs index 0118906..5ddcb33 100644 --- a/workspace/src/part08.rs +++ b/workspace/src/part08.rs @@ -56,11 +56,11 @@ impl ops::Add for BigInt { } } -// ## Traits and borrowed types +// ## Traits and reference types // Writing this out becomes a bit tedious, because trait implementations (unlike functions) require full explicit annotation // of lifetimes. Make sure you understand exactly what the following definition says. Notice that we can implement a trait for -// a borrowed type! +// a reference type! impl<'a, 'b> ops::Add<&'a BigInt> for &'b BigInt { type Output = BigInt; fn add(self, rhs: &'a BigInt) -> Self::Output { diff --git a/workspace/src/part11.rs b/workspace/src/part11.rs index 746aed9..a93ad07 100644 --- a/workspace/src/part11.rs +++ b/workspace/src/part11.rs @@ -59,7 +59,7 @@ pub fn main() { } -// **Exercise 11.1**: We made the arbitrary choice of using `i32` for the arguments. Generalize the data-structures above +// **Exercise 11.1**: We made the arbitrary choice of using `i32` for the arguments. Generalize the data structures above // to work with an arbitrary type `T` that's passed to the callbacks. Since you need to call multiple callbacks with the -// same `t: T`, you will either have to restrict `T` to `Copy` types, or pass a borrow. +// same `t: T`, you will either have to restrict `T` to `Copy` types, or pass a reference. diff --git a/workspace/src/part12.rs b/workspace/src/part12.rs index 86fb4bb..390b5df 100644 --- a/workspace/src/part12.rs +++ b/workspace/src/part12.rs @@ -48,7 +48,7 @@ fn demo_cell(c: &mut Callbacks) { let count = Cell::new(0); // Again, we have to move ownership if the `count` into the environment closure. c.register(move |val| { - // In here, all we have is a shared borrow of our environment. But that's good enough for the `get` and `set` of the cell! + // In here, all we have is a shared reference of our environment. But that's good enough for the `get` and `set` of the cell! let new_count = count.get()+1; count.set(new_count); println!("Callback 2: {} ({}. time)", val, new_count); @@ -81,7 +81,7 @@ impl CallbacksMut { // We have to *explicitly* borrow the contents of a `RefCell` by calling `borrow` or `borrow_mut`. let mut closure = callback.borrow_mut(); // Unfortunately, Rust's auto-dereference of pointers is not clever enough here. We thus have to explicitly - // dereference the smart pointer and obtain a mutable borrow of the content. + // dereference the smart pointer and obtain a mutable reference to the content. (&mut *closure)(val); } } @@ -104,5 +104,5 @@ fn demo_mut(c: &mut CallbacksMut) { } // **Exercise 12.1**: Write some piece of code using only the available, public interface of `CallbacksMut` such that a reentrant call to a closure -// is happening, and the program aborts because the `RefCell` refuses to hand out a second mutable borrow of the closure's environment. +// is happening, and the program panics because the `RefCell` refuses to hand out a second mutable borrow of the closure's environment. diff --git a/workspace/src/part16.rs b/workspace/src/part16.rs index 21cd60f..fa5b184 100644 --- a/workspace/src/part16.rs +++ b/workspace/src/part16.rs @@ -80,7 +80,7 @@ impl<'a, T> Iterator for IterMut<'a, T> { if self.next.is_null() { None } else { - // Otherwise, we can convert the next pointer to a borrow, get a borrow to the data + // Otherwise, we can convert the next pointer to a reference, get a reference to the data // and update the iterator. let next = unsafe { &mut *self.next }; let ret = &mut next.data; @@ -90,14 +90,14 @@ impl<'a, T> Iterator for IterMut<'a, T> { } -// **Exercise 16.2**: Add a method `iter` and a type `Iter` providing iteration for shared borrows. +// **Exercise 16.2**: Add a method `iter` and a type `Iter` providing iteration for shared references. // Add testcases for both kinds of iterators. // ## `Drop` impl Drop for LinkedList { // The destructor itself is a method which takes `self` in mutably borrowed form. It cannot own `self`, because then - // the destructor of `self` would be called at the end of the function, resulting in endless recursion... + // the destructor of `self` would be called at the end of the function, resulting in endless recursion. fn drop(&mut self) { let mut cur_ptr = self.first; while !cur_ptr.is_null() {