//
// ### 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)
-// 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
//@ 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<i32>`.
+//@ So, let's re-write `vec_min` to work on a shared reference to a vector, written `&Vec<i32>`.
//@ I also took the liberty to convert the function from `SomethingOrNothing` to the standard
//@ library type `Option`.
fn vec_min(v: &Vec<i32>) -> Option<i32> {
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 {
}
// 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<i32>` is the type of mutable borrows of `vec<i32>`. Because the borrow is
-//@ mutable, we can use a mutable iterator, providing a mutable borrow of the elements.
+//@ The type `&mut Vec<i32>` is the type of mutable references to `vec<i32>`. Because the reference is
+//@ mutable, we can use a mutable iterator, providing mutable (exclusive) references to the elements.
fn vec_inc(v: &mut Vec<i32>) {
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.
//@ 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!
//@ `#[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) {
// Now we can write `vec_min`.
fn vec_min(v: &Vec<BigInt>) -> Option<BigInt> {
let mut min: Option<BigInt> = 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 { /*@*/
//@ the intermediate variable `min`, which also has type `Option<BigInt>`. 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`. <br/>
//@ 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.
// ## 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<T>(v: &Vec<T>) -> Option<&T> {
if v.len() > 0 {
Some(&v[0]) /*@*/
*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<i32>`, 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<T>) -> 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)
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;
}
}
-// ## 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 {
//@ 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. <br/>
//@ `usize` here is the type of unsigned, pointer-sized numbers. It is typically the type of "lengths of things",
// 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() } /*@*/
// ## 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.)
//@ 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>;
}
// With this in place, you can now replace `b.iter()` in `main` by `&b`. Go ahead and try it! <br/>
//@ 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
//@ 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);
}
impl BigInt {
fn act_v1<A: Action>(&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); /*@*/
}
//@ 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`.
// 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);
}
// 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`.
// Since they are of type `FnMut`, we need to mutably iterate.
for callback in self.callbacks.iter_mut() {
//@ Here, `callback` has type `&mut Box<FnMut(i32)>`. 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.
}
}
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;
//@ ## 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).
//@ (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)
//@ 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<Rc<Fn(i32)>>,
//@ 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<T>`. 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*. <br/>
//@ Notice that it is impossible to *borrow* the contents of the cell, and that is actually the key to why this is safe.
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;
}
//@ 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`
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. <br />
- //@ 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);
}
}
}
// **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)
//@ 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
//@ `[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<T: PartialOrd>(data: &mut [T]) {
if data.len() < 2 { return; }
// 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
//@ 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
*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!
//@
//@ ## `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.
//@ `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<T>` is only `Send` if `T` is `Sync`.
+//@ Since `Arc` provides multiple threads with a shared reference to its content, `Arc<T>` 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<T>` 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<T>` 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
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<T> = *mut Node<T>;
//@ 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.
//@ 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 {
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;
//@ 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`
//@ of `LinkedList`.
impl<T> Drop for LinkedList<T> {
// 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() {
-// Rust-101, Part 04: Ownership, Borrowing
-// =======================================
+// Rust-101, Part 04: Ownership, Borrowing, References
+// ===================================================
/*
void foo(std::vector<int> v) {
/* println!("The first element is: {}", v[0]); */ /* BAD! */
}
-// ## Shared borrowing
+// ## Borrowing a shared reference
fn vec_min(v: &Vec<i32>) -> Option<i32> {
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 {
}
// 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);
println!("The first element is: {}", *first);
}
-// ## Mutable borrowing
+// ## Exclusive, mutable references
fn vec_inc(v: &mut Vec<i32>) {
for e in v.iter_mut() {
}
}
// 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);
// 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.
}
// **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 {
// Now we can write `vec_min`.
fn vec_min(v: &Vec<BigInt>) -> Option<BigInt> {
let mut min: Option<BigInt> = 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!()
}
}
-// ## 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 {
}
-// **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.
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);
// 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);
}
}
}
// **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.
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;
}
-// **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<T> Drop for LinkedList<T> {
// 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() {