diff --git a/ch04-02-references-and-borrowing.html b/ch04-02-references-and-borrowing.html index 028022721b..76590a35c7 100644 --- a/ch04-02-references-and-borrowing.html +++ b/ch04-02-references-and-borrowing.html @@ -217,7 +217,7 @@

The macro vec! creates a vector with the elements between the brackets. The vector v has type Vec<i32>. The syntax <i32> means the elements of the vector have type i32.

One important implementation detail is that v allocates a heap array of a certain capacity. We can peek into Vec’s internals and see this detail for ourselves:

-
+

Note: click the binocular icon in the top right of the diagram to toggle this detailed view in any runtime diagram.

@@ -278,7 +278,7 @@

The Borrow Checker Finds Permission Violations

Recall the Pointer Safety Principle: data should not be aliased and mutated. The goal of these permissions is to ensure that data cannot be mutated if it is aliased. Creating a reference to data (“borrowing” it) causes that data to be temporarily read-only until the reference is no longer in use.

Rust uses these permissions in its borrow checker. The borrow checker looks for potentially unsafe operations involving references. Let’s return to the unsafe program we saw earlier, where push invalidates a reference. This time we’ll add another aspect to the permissions diagram:

-
+

Any time a place is used, Rust expects that place to have certain permissions depending on the operation. For example, the borrow &v[2] requires that v is readable. Therefore the R permission is shown between the operation & and the place v. The letter is filled-in because v has the read permission at that line.

By contrast, the mutating operation v.push(4) requires that v is readable and writable. Both R and W are shown. However, v does not have write permissions (it is borrowed by num). So the letter W is hollow, indicating that the write permission is expected but v does not have it.

If you try to compile this program, then the Rust compiler will return the following error:

@@ -306,7 +306,7 @@

The first observation is what makes mutable references safe. Mutable references allow mutation but prevent aliasing. The borrowed place v becomes temporarily unusable, so effectively not an alias.

The second observation is what makes mutable references useful. v[2] can be mutated through *num. For example, *num += 1 mutates v[2]. Note that *num has the W permission, but num does not. num refers to the mutable reference itself, e.g. num cannot be reassigned to a different mutable reference.

Mutable references can also be temporarily “downgraded” to read-only references. For example:

-
+

Note: when permission changes are not relevant to an example, we will hide them. You can view hidden steps by clicking “»”, and you can view hidden permissions within a step by clicking “● ● ●”.

@@ -317,13 +317,13 @@

The W permission on x is returned to x after the lifetime of y has ended, like we have seen before.

In the previous examples, a lifetime has been a contiguous region of code. However, once we introduce control flow, this is not necessarily the case. For example, here is a function that capitalizes the first character in a vector of ASCII characters:

-
+

The variable c has a different lifetime in each branch of the if-statement. In the then-block, c is used in the expression c.to_ascii_uppercase(). Therefore *v does not regain the W permission until after that line.

However, in the else-block, c is not used. *v immediately regains the W permission on entry to the else-block.

Data Must Outlive All Of Its References

As a part of the Pointer Safety Principle, the borrow checker enforces that data must outlive any references to it. Rust enforces this property in two ways. The first way deals with references that are created and dropped within the scope of a single function. For example, say we tried to drop a string while holding a reference to it:

-
+

To catch these kinds of errors, Rust uses the permissions we’ve already discussed. The borrow &s removes the O permission from s. However, drop expects the O permission, leading to a permission mismatch.

The key idea is that in this example, Rust knows how long s_ref lives. But Rust needs a different enforcement mechanism when it doesn’t know how long a reference lives. Specifically, when references are either input to a function, or output from a function. For example, here is a safe function that returns a reference to the first element in a vector:

@@ -350,7 +350,7 @@

“Validating References with Lifetimes”. For now, it’s enough to know that: (1) input/output references are treated differently than references within a function body, and (2) Rust uses a different mechanism, the F permission, to check the safety of those references.

To see the F permission in another context, say you tried to return a reference to a variable on the stack like this:

-
+

This program is unsafe because the reference &s will be invalidated when return_a_string returns. And Rust will reject this program with a similar missing lifetime specifier error. Now you can understand that error means that s_ref is missing the appropriate flow permissions.

Summary

diff --git a/ch04-03-fixing-ownership-errors.html b/ch04-03-fixing-ownership-errors.html index bb212e7eb3..5d3f2c9498 100644 --- a/ch04-03-fixing-ownership-errors.html +++ b/ch04-03-fixing-ownership-errors.html @@ -197,7 +197,7 @@

Fixing an Unsafe Program: Not Enough Permissions

Another common issue is trying to mutate read-only data, or trying to drop data behind a reference. For example, let’s say we tried to write a function stringify_name_with_title. This function is supposed to create a person’s full name from a vector of name parts, including an extra title.

-
+

This program is rejected by the borrow checker because name is an immutable reference, but name.push(..) requires the W permission. This program is unsafe because push could invalidate other references to name outside of stringify_name_with_title, like this:

In this example, a reference first to name[0] is created before calling stringify_name_with_title. The function name.push(..) reallocates the contents of name, which invalidates first, causing the println to read deallocated memory.

@@ -233,7 +233,7 @@

Fixing an Unsafe Program: Aliasing and Mutating a Data Structure

Another unsafe operation is using a reference to heap data that gets deallocated by another alias. For example, here’s a function that gets a reference to the largest string in a vector, and then uses it while mutating the vector:

-
+

Note: this example uses iterators and closures to succinctly find a reference to the largest string. We will discuss those features in later chapters, and for now we will provide an intuitive sense of how the features work here.

@@ -278,7 +278,7 @@

These solutions all share in common the key idea: shortening the lifetime of borrows on dst to not overlap with a mutation to dst.

Fixing an Unsafe Program: Copying vs. Moving Out of a Collection

A common confusion for Rust learners happens when copying data out of a collection, like a vector. For example, here’s a safe program that copies a number out of a vector:

-
+

The dereference operation *n_ref expects just the R permission, which the path *n_ref has. But what happens if we change the type of elements in the vector from i32 to String? Then it turns out we no longer have the necessary permissions:

The first program will compile, but the second program will not compile. Rust gives the following error message:

@@ -336,10 +336,10 @@

Fixing a Safe Program: Mutating Different Tuple Fields

The above examples are cases where a program is unsafe. Rust may also reject safe programs. One common issue is that Rust tries to track permissions at a fine-grained level. However, Rust may conflate two different places as the same place.

Let’s first look at an example of fine-grained permission tracking that passes the borrow checker. This program shows how you can borrow one field of a tuple, and write to a different field of the same tuple:

-
+

The statement let first = &name.0 borrows name.0. This borrow removes WO permissions from name.0. It also removes WO permissions from name. (For example, one could not pass name to a function that takes as input a value of type (String, String).) But name.1 still retains the W permission, so doing name.1.push_str(...) is a valid operation.

However, Rust can lose track of exactly which places are borrowed. For example, let’s say we refactor the expression &name.0 into a function get_first. Notice how after calling get_first(&name), Rust now removes the W permission on name.1:

-
+

Now we can’t do name.1.push_str(..)! Rust will return this error:

error[E0502]: cannot borrow `name.1` as mutable because it is also borrowed as immutable
   --> test.rs:11:5
@@ -361,7 +361,7 @@ 

let idx = a_complex_function(); let x = &mut a[idx];

What is the value of idx? Rust isn’t going to guess, so it assumes idx could be anything. For example, let’s say we try to read from one array index while writing to a different one:

-
+

However, Rust will reject this program because a gave its read permission to x. The compiler’s error message says the same thing:

error[E0502]: cannot borrow `a[_]` as immutable because it is also borrowed as mutable
  --> test.rs:4:9
diff --git a/ch04-04-slices.html b/ch04-04-slices.html
index 81fe870000..b6d7099a4c 100644
--- a/ch04-04-slices.html
+++ b/ch04-04-slices.html
@@ -245,7 +245,7 @@ 

The Slice Type< will still be valid in the future. Consider the program in Listing 4-8 that uses the first_word function from Listing 4-7.

Filename: src/main.rs

-
+

Listing 4-8: Storing the result from calling the first_word function and then changing the String contents

This program compiles without any errors, as s retains write permissions @@ -355,7 +355,7 @@

Filename: src/main.rs

-
+

You can see that calling first_word now removes the write permission from s, which prevents us from calling s.clear(). Here’s the compiler error:

$ cargo run
diff --git a/ch04-05-ownership-recap.html b/ch04-05-ownership-recap.html
index bb21d7b2ed..dbc43db3cf 100644
--- a/ch04-05-ownership-recap.html
+++ b/ch04-05-ownership-recap.html
@@ -255,9 +255,9 @@ 

Own

If you want to review slices, re-read Chapter 4.4.

Ownership at Compile-time

Rust tracks R (read), W (write), and O (own) permissions on each variable. Rust requires that a variable has appropriate permissions to perform a given operation. As a basic example, if a variable is not declared as let mut, then it is missing the W permission and cannot be mutated:

-
+

A variable’s permissions can be changed if it is moved or borrowed. A move of a variable with a non-copyable type (like Box<T> or String) requires the RO permissions, and the move eliminates all permissions on the variable. That rule prevents the use of moved variables:

-
+

If you want to review how moves work, re-read Chapter 4.1.

Borrowing a variable (creating a reference to it) temporarily removes some of the variable’s permissions. An immutable borrow creates an immutable reference, and also disables the borrowed data from being mutated or moved. For example, printing an immutable reference is ok:

@@ -266,11 +266,11 @@

And moving data out of the reference is not ok:

-
+

A mutable borrow creates a mutable reference, which disables the borrowed data from being read, written, or moved. For example, mutating a mutable reference is ok:

-
+

But accessing the mutably borrowed data is not ok:

-
+

If you want to review permissions and references, re-read Chapter 4.2.

Connecting Ownership between Compile-time and Runtime

Rust’s permissions are designed to prevent undefined behavior. For example, one kind of undefined behavior is a use-after-free where freed memory is read or written. Immutable borrows remove the W permission to avoid use-after-free, like in this case:

@@ -281,7 +281,7 @@

The Rest of Ownership

As we introduce additional features like structs, enums, and traits, those features will have specific interactions with ownership. This chapter provides the essential foundation for understanding those interactions — the concepts of memory, pointers, undefined behavior, and permissions will help us talk about the more advanced parts of Rust in future chapters.

And don’t forget to take the quizzes if you want to check your understanding!

-
+
1

In fact, the original invention of ownership types wasn’t about memory safety at all. It was about preventing leaks of mutable references to data structure internals in Java-like languages. If you’re curious to learn more about the history of ownership types, check out the paper “Ownership Types for Flexible Alias Protection” (Clarke et al. 1998).

diff --git a/ch05-01-defining-structs.html b/ch05-01-defining-structs.html index 3679a67345..6c374f0b7f 100644 --- a/ch05-01-defining-structs.html +++ b/ch05-01-defining-structs.html @@ -427,9 +427,9 @@

Borrowing Fields of a Struct

Similar to our discussion in “Different Tuple Fields”, Rust’s borrow checker will track ownership permissions at both the struct-level and field-level. For example, if we borrow a field x of a Point structure, then both p and p.x temporarily lose their permissions (but not p.y):

-
+

As a result, if we try and use p while p.x is mutably borrowed like this:

-
+

Then the compiler will reject our program with the following error:

error[E0502]: cannot borrow `p` as immutable because it is also borrowed as mutable
   --> test.rs:10:17
diff --git a/ch05-03-method-syntax.html b/ch05-03-method-syntax.html
index babaee2757..be7a7e006d 100644
--- a/ch05-03-method-syntax.html
+++ b/ch05-03-method-syntax.html
@@ -542,10 +542,10 @@ 

+

Moves with self

Calling a method that expects self will move the input struct (unless the struct implements Copy). For example, we cannot use a Rectangle after passing it to max:

-
+

Once we call rect.max(..), we move rect and so lose all permissions on it. Trying to compile this program would give us the following error:

error[E0382]: borrow of moved value: `rect`
   --> test.rs:33:16
@@ -559,7 +559,7 @@ 

Moves with

A similar situation arises if we try to call a self method on a reference. For instance, say we tried to make a method set_to_max that assigns self to the output of self.max(..):

-
+

Then we can see that self is missing O permissions in the operation self.max(..). Rust therefore rejects this program with the following error:

error[E0507]: cannot move out of `*self` which is behind a mutable reference
   --> test.rs:23:17
diff --git a/ch06-02-match.html b/ch06-02-match.html
index 599b3d17bb..d138933fb6 100644
--- a/ch06-02-match.html
+++ b/ch06-02-match.html
@@ -507,12 +507,12 @@ 

How Matches Interact with Ownership

If an enum contains non-copyable data like a String, then you should be careful with whether a match will move or borrow that data. For example, this program using an Option<String> will compile:

-
+

But if we replace the placeholder in Some(_) with a variable name, like Some(s), then the program will NOT compile:

-
+

opt is a plain enum — its type is Option<String> and not a reference like &Option<String>. Therefore a match on opt will move non-ignored fields like s. Notice how opt loses read and own permission sooner in the second program compared to the first. After the match expression, the data within opt has been moved, so it is illegal to read opt in the println.

If we want to peek into opt without moving its contents, the idiomatic solution is to match on a reference:

-
+

Rust will “push down” the reference from the outer enum, &Option<String>, to the inner field, &String. Therefore s has type &String, and opt can be used after the match. To better understand this “pushing down” mechanism, see the section about binding modes in the Rust Reference.

diff --git a/ch13-01-closures.html b/ch13-01-closures.html index 5033e522a0..1c8c89ed1f 100644 --- a/ch13-01-closures.html +++ b/ch13-01-closures.html @@ -746,7 +746,7 @@

}

These changes say: s_ref is a string reference that lives for 'a. Adding + 'a to the return type’s trait bounds indicates that the closure must live no longer than 'a. Therefore Rust deduces this function is now safe. If we try to use it unsafely like before:

-
+

Rust recognizes that as long as make_a_cloner is in use, s_own cannot be dropped. This is reflected in the permissions: s_own loses the O permission after calling make_a_cloner. Consequently, Rust rejects this program with the following error:

error[E0505]: cannot move out of `s_own` because it is borrowed
   --> test.rs:9:6
diff --git a/experiment-intro.html b/experiment-intro.html
index 64d194dbec..fe534bf120 100644
--- a/experiment-intro.html
+++ b/experiment-intro.html
@@ -173,6 +173,11 @@ 

2. Highlighting

3. …and more!

The book’s content may change as you go through the experiment. We will update this page as we add new features. Here’s the changelog: