diff --git a/nostarch/chapter04.md b/nostarch/chapter04.md index ce2416819..8334bf120 100644 --- a/nostarch/chapter04.md +++ b/nostarch/chapter04.md @@ -3,29 +3,29 @@ # Understanding Ownership -Ownership is Rust’s most unique feature, and has deep implications for the rest +Ownership is Rust’s most unique feature and has deep implications for the rest of the language. It enables Rust to make memory safety guarantees without needing a garbage collector, so it’s important to understand how ownership -works in Rust. In this chapter, we’ll talk about ownership as well as several -related features: borrowing, slices, and how Rust lays data out in memory. +works. In this chapter, we’ll talk about ownership as well as several related +features: borrowing, slices, and how Rust lays data out in memory. ## What Is Ownership? - -Ownership in Rust is a set of rules that governs how a program manages memory. +*Ownership* is a set of rules that governs how a Rust program manages memory. All programs have to manage the way they use a computer’s memory while running. Some languages have garbage collection that constantly looks for no-longer used memory as the program runs; in other languages, the programmer must explicitly allocate and free the memory. Rust uses a third approach: memory is managed -through a system of ownership with a set of rules that the compiler checks at -compile time. If any of the rules are violated, the program XXX +through a system of ownership with a set of rules that the compiler checks. If +any of the rules are violated, the program won’t compile. + None of the features of ownership will slow down your program while it’s running. Because ownership is a new concept for many programmers, it does take some time to get used to. The good news is that the more experienced you become with Rust -and the rules of the ownership system, the easier you'll find it to naturally +and the rules of the ownership system, the easier you’ll find it to naturally develop code that is safe and efficient. Keep at it! When you understand ownership, you’ll have a solid foundation for understanding @@ -35,44 +35,44 @@ strings. > ### The Stack and the Heap > -> Most programming languages don't requrie you to think about the stack and -> the heap very often. But in a systems programming language like Rust, whether -> a value is on the stack or the heap effects how the language -> behaves and why you have to make certain decisions. Parts of ownership will -> be described in relation to the stack and the heap later in this chapter, so -> here is a brief explanation in preparation. +> Many programming languages don’t require you to think about the stack and the +> heap very often. But in a systems programming language like Rust, whether a +> value is on the stack or the heap affects how the language behaves and why +> you have to make certain decisions. Parts of ownership will be described in +> relation to the stack and the heap later in this chapter, so here is a brief +> explanation in preparation. > -> Both the stack and the heap are parts of memory available to your -> code to use at runtime, but they are structured in different ways. The stack -> stores values in the order it gets them and removes the values in the -> opposite order. This is referred to as *last in, first out*. Think of a stack -> of plates: when you add more plates, you put them on top of the pile, and -> when you need a plate, you take one off the top. Adding or removing plates -> from the middle or bottom wouldn’t work as well! Adding data is called -> *pushing onto the stack*, and removing data is called *popping off the stack*. -> All data stored on the stack must have a known, fixed size. Data with an -> unknown size at compile time or a size that might change must be stored on -> the heap instead. +> Both the stack and the heap are parts of memory available to your code to use +> at runtime, but they are structured in different ways. The stack stores +> values in the order it gets them and removes the values in the opposite +> order. This is referred to as *last in, first out*. Think of a stack of +> plates: when you add more plates, you put them on top of the pile, and when +> you need a plate, you take one off the top. Adding or removing plates from +> the middle or bottom wouldn’t work as well! Adding data is called *pushing +> onto the stack*, and removing data is called *popping off the stack*. All +> data stored on the stack must have a known, fixed size. Data with an unknown +> size at compile time or a size that might change must be stored on the heap +> instead. > -> The heap is less organized: when you put data on the heap, -> you request a certain amount of space. The memory allocator finds an empty -> spot in the heap that is big enough, marks it as being in use, and returns a -> *pointer*, which is the address of that location. This process is called -> *allocating on the heap* and is sometimes abbreviated as just *allocating*. -> Pushing values onto the stack is not considered allocating. Because the -> pointer to the heap is a known, fixed size, you can store the pointer on the stack, but -> when you want the actual data, you must follow the pointer. Think of being -> seated at a restaurant. When you enter, you state the number of -> people in your group, and the staff finds an empty table that fits everyone -> and leads you there. If someone in your group comes late, they can ask where -> you’ve been seated to find you. +> The heap is less organized: when you put data on the heap, you request a +> certain amount of space. The memory allocator finds an empty spot in the heap +> that is big enough, marks it as being in use, and returns a *pointer*, which +> is the address of that location. This process is called *allocating on the +> heap* and is sometimes abbreviated as just *allocating*. Pushing values onto +> the stack is not considered allocating. Because the pointer to the heap is a +> known, fixed size, you can store the pointer on the stack, but when you want +> the actual data, you must follow the pointer. Think of being seated at a +> restaurant. When you enter, you state the number of people in your group, and +> the staff finds an empty table that fits everyone and leads you there. If +> someone in your group comes late, they can ask where you’ve been seated to +> find you. > > Pushing to the stack is faster than allocating on the heap because the -> allocator never has to search for a place to store new data; that -> location is always at the top of the stack. Comparatively, allocating space -> on the heap requires more work, because the allocator must first find -> a big enough space to hold the data and then perform bookkeeping to prepare -> for the next allocation. +> allocator never has to search for a place to store new data; that location is +> always at the top of the stack. Comparatively, allocating space on the heap +> requires more work, because the allocator must first find a big enough space +> to hold the data and then perform bookkeeping to prepare for the next +> allocation. > > Accessing data in the heap is slower than accessing data on the stack because > you have to follow a pointer to get there. Contemporary processors are faster @@ -94,8 +94,8 @@ strings. > minimizing the amount of duplicate data on the heap, and cleaning up unused > data on the heap so you don’t run out of space are all problems that ownership > addresses. Once you understand ownership, you won’t need to think about the -> stack and the heap very often, but knowing that the main purpose of ownership is to -> manage heap data can help explain why it works the way it does. +> stack and the heap very often, but knowing that the main purpose of ownership +> is to manage heap data can help explain why it works the way it does. ### Ownership Rules @@ -108,15 +108,15 @@ work through the examples that illustrate them: ### Variable Scope -Now that we’re past basic Rust syntax, we won’t include all the `fn main() {` code in -examples, so if you’re following along, make sure to put the following +Now that we’re past basic Rust syntax, we won’t include all the `fn main() {` +code in examples, so if you’re following along, make sure to put the following examples inside a `main` function manually. As a result, our examples will be a bit more concise, letting us focus on the actual details rather than boilerplate code. As a first example of ownership, we’ll look at the *scope* of some variables. A -scope is the range within a program for which an item is valid. Take the following -variable: +scope is the range within a program for which an item is valid. Take the +following variable: ``` let s = "hello"; @@ -124,15 +124,15 @@ let s = "hello"; The variable `s` refers to a string literal, where the value of the string is hardcoded into the text of our program. The variable is valid from the point at -which it’s declared until the end of the current *scope*. Listing 4-1 shows a pseudoprogram -with comments annotating where the variable `s` would be valid. +which it’s declared until the end of the current *scope*. Listing 4-1 shows a +program with comments annotating where the variable `s` would be valid. ``` - { // s is not valid here, it’s not yet declared - let s = "hello"; // s is valid from this point forward +{ // s is not valid here, it’s not yet declared + let s = "hello"; // s is valid from this point forward. - // do stuff with s - } // this scope is now over, and s is no longer valid + // do stuff with s +} // this scope is now over, and s is no longer valid ``` Listing 4-1: A variable and the scope in which it is valid @@ -154,12 +154,13 @@ covered previously are all a known size, can be stored on the stack and popped off the stack when their scope is over, and can be quickly and trivially copied to make a new, independent instance if another part of code needs to use the same value in a different scope. But we want to look at data that is stored on -the heap and explore how Rust knows when to clean up that data. +the heap and explore how Rust knows when to clean up that data, and the +`String` type is a great example. -We’ll concentrate on the parts of `String` -that relate to ownership. These aspects also apply to other complex data types, -whether they are provided by the standard library or created by you. We’ll -discuss `String` in more depth in Chapter 8. +We’ll concentrate on the parts of `String` that relate to ownership. These +aspects also apply to other complex data types, whether they are provided by +the standard library or created by you. We’ll discuss `String` in more depth in +Chapter 8. We’ve already seen string literals, where a string value is hardcoded into our program. String literals are convenient, but they aren’t suitable for every @@ -175,20 +176,20 @@ literal using the `from` function, like so: let s = String::from("hello"); ``` -The double colon `::` operator allows us to namespace this -particular `from` function under the `String` type rather than using some sort -of name like `string_from`. We’ll discuss this syntax more in the “Method -Syntax” section of Chapter 5 and when we talk about namespacing with modules in -“Paths for Referring to an Item in the Module Tree” in Chapter 7. +The double colon `::` operator allows us to namespace this particular `from` +function under the `String` type rather than using some sort of name like +`string_from`. We’ll discuss this syntax more in the “Method Syntax” section of +Chapter 5 and when we talk about namespacing with modules in “Paths for +Referring to an Item in the Module Tree” in Chapter 7. This kind of string *can* be mutated: ``` - let mut s = String::from("hello"); +let mut s = String::from("hello"); - s.push_str(", world!"); // push_str() appends a literal to a String +s.push_str(", world!"); // push_str() appends a literal to a String - println!("{}", s); // This will print `hello, world!` +println!("{}", s); // This will print `hello, world!` ``` So, what’s the difference here? Why can `String` be mutated but literals @@ -216,25 +217,26 @@ requests the memory it needs. This is pretty much universal in programming languages. However, the second part is different. In languages with a *garbage collector -(GC)*, the GC keeps track of and cleans up memory that isn’t being used anymore, -and we don’t need to think about it. In most languages without a GC, it’s our responsibility to -identify when memory is no longer being used and call code to explicitly return -it, just as we did to request it. Doing this correctly has historically been a -difficult programming problem. If we forget, we’ll waste memory. If we do it -too early, we’ll have an invalid variable. If we do it twice, that’s a bug too. -We need to pair exactly one `allocate` with exactly one `free`. +(GC)*, the GC keeps track of and cleans up memory that isn’t being used +anymore, and we don’t need to think about it. In most languages without a GC, +it’s our responsibility to identify when memory is no longer being used and +call code to explicitly return it, just as we did to request it. Doing this +correctly has historically been a difficult programming problem. If we forget, +we’ll waste memory. If we do it too early, we’ll have an invalid variable. If +we do it twice, that’s a bug too. We need to pair exactly one `allocate` with +exactly one `free`. Rust takes a different path: the memory is automatically returned once the variable that owns it goes out of scope. Here’s a version of our scope example from Listing 4-1 using a `String` instead of a string literal: ``` - { - let s = String::from("hello"); // s is valid from this point forward +{ + let s = String::from("hello"); // s is valid from this point forward - // do stuff with s - } // this scope is now over, and s is no - // longer valid + // do stuff with s +} // this scope is now over, and s is no + // longer valid ``` There is a natural point at which we can return the memory our `String` needs @@ -278,9 +280,9 @@ Now let’s look at the `String` version: let s2 = s1; ``` -This looks very similar, so we might assume that the way -it works would be the same: that is, the second line would make a copy of the -value in `s1` and bind it to `s2`. But this isn’t quite what happens. +This looks very similar, so we might assume that the way it works would be the +same: that is, the second line would make a copy of the value in `s1` and bind +it to `s2`. But this isn’t quite what happens. Take a look at Figure 4-1 to see what is happening to `String` under the covers. A `String` is made up of three parts, shown on the left: a pointer to @@ -290,7 +292,8 @@ heap that holds the contents. String in memory -Figure 4-1: Representation in memory of a `String` holding the value `"hello"` bound to `s1` +Figure 4-1: Representation in memory of a `String` holding the value `"hello"` +bound to `s1` The length is how much memory, in bytes, the contents of the `String` is currently using. The capacity is the total amount of memory, in bytes, that the @@ -305,7 +308,8 @@ representation in memory looks like Figure 4-2. s1 and s2 pointing to the same value -Figure 4-2: Representation in memory of the variable `s2` that has a copy of the pointer, length, and capacity of `s1` +Figure 4-2: Representation in memory of the variable `s2` that has a copy of +the pointer, length, and capacity of `s1` The representation does *not* look like Figure 4-3, which is what memory would look like if Rust instead copied the heap data as well. If Rust did this, the @@ -314,7 +318,8 @@ the data on the heap were large. s1 and s2 to two places -Figure 4-3: Another possibility for what `s2 = s1` might do if Rust copied the heap data as well +Figure 4-3: Another possibility for what `s2 = s1` might do if Rust copied the +heap data as well Earlier, we said that when a variable goes out of scope, Rust automatically calls the `drop` function and cleans up the heap memory for that variable. But @@ -324,24 +329,22 @@ same memory. This is known as a *double free* error and is one of the memory safety bugs we mentioned previously. Freeing memory twice can lead to memory corruption, which can potentially lead to security vulnerabilities. -To ensure memory safety, after the line `let s2 = s1`, Rust considers `s1` as no longer -valid. Therefore, Rust doesn’t need to free anything when `s1` goes out of -scope. Check out what happens when you try to use `s1` after `s2` is created; -it won’t work: +To ensure memory safety, after the line `let s2 = s1`, Rust considers `s1` as +no longer valid. Therefore, Rust doesn’t need to free anything when `s1` goes +out of scope. Check out what happens when you try to use `s1` after `s2` is +created; it won’t work: ``` - let s1 = String::from("hello"); - let s2 = s1; +let s1 = String::from("hello"); +let s2 = s1; - println!("{}, world!", s1); +println!("{}, world!", s1); ``` You’ll get an error like this because Rust prevents you from using the invalidated reference: ``` -$ cargo run - Compiling ownership v0.1.0 (file:///projects/ownership) error[E0382]: borrow of moved value: `s1` --> src/main.rs:5:28 | @@ -357,7 +360,7 @@ error[E0382]: borrow of moved value: `s1` If you’ve heard the terms *shallow copy* and *deep copy* while working with other languages, the concept of copying the pointer, length, and capacity without copying the data probably sounds like making a shallow copy. But -because Rust also invalidates the first variable, instead ofcalling it a +because Rust also invalidates the first variable, instead of calling it a shallow copy, it’s known as a *move*. In this example, we would say that `s1` was *moved* into `s2`. So what actually happens is shown in Figure 4-4. @@ -382,10 +385,10 @@ programming languages, you’ve probably seen them before. Here’s an example of the `clone` method in action: ``` - let s1 = String::from("hello"); - let s2 = s1.clone(); +let s1 = String::from("hello"); +let s2 = s1.clone(); - println!("s1 = {}, s2 = {}", s1, s2); +println!("s1 = {}, s2 = {}", s1, s2); ``` This works just fine and explicitly produces the behavior shown in Figure 4-3, @@ -401,10 +404,10 @@ There’s another wrinkle we haven’t talked about yet. This code using integer part of which was shown in Listing 4-2 – works and is valid: ``` - let x = 5; - let y = x; +let x = 5; +let y = x; - println!("x = {}, y = {}", x, y); +println!("x = {}, y = {}", x, y); ``` But this code seems to contradict what we just learned: we don’t have a call to @@ -418,16 +421,20 @@ between deep and shallow copying here, so calling `clone` wouldn’t do anything different from the usual shallow copying and we can leave it out. Rust has a special annotation called the `Copy` trait that we can place on -types that are stored on the stack, like integers (we’ll talk more about traits -in Chapter 10). If a type implements the `Copy` trait, an older variable is -still usable after assignment. +types that are stored on the stack like integers are (we’ll talk more about +traits in Chapter 10). If a type implements the `Copy` trait, a variable is +still valid after assignment to another variable. -Rust won’t let us annotate a type with -`Copy` if the type, or any of its parts, has implemented the `Drop` -trait. If the type needs something special to happen when the value goes out of -scope and we add the `Copy` annotation to that type, we’ll get a compile-time -error. To learn about how to add the `Copy` annotation to your type to -implement the trait, see “Derivable Traits” in Appendix C. + +Rust won’t let us annotate a type with `Copy` if the type, or any of its parts, +has implemented the `Drop` trait. If the type needs something special to happen +when the value goes out of scope and we add the `Copy` annotation to that type, +we’ll get a compile-time error. To learn about how to add the `Copy` annotation +to your type to implement the trait, see “Derivable Traits” in Appendix C. So what types implement the `Copy` trait? You can check the documentation for the given type to be sure, but as a general rule, any group of simple scalar @@ -486,8 +493,9 @@ the ownership rules prevent you from doing so. ### Return Values and Scope -Returning values can also transfer ownership. In Listing 4-4 we give an example -with of a function that returns some value, with similar annotations to those in Listing 4-3. +Returning values can also transfer ownership. Listing 4-4 shows an example +of a function that returns some value, with similar annotations as those in +Listing 4-3. Filename: src/main.rs @@ -527,16 +535,16 @@ Listing 4-4: Transferring ownership of return values The ownership of a variable follows the same pattern every time: assigning a value to another variable moves it. When a variable that includes data on the -heap goes out of scope, the value will be cleaned up by `drop` unless the data -has been moved in such a way that it is then owned by another variable. +heap goes out of scope, the value will be cleaned up by `drop` unless ownership +of the data has been moved to another variable. -While this works, taking ownership and then returning ownership with every function is a bit -tedious. What if we want to let a function use a value but not take ownership? -It’s quite annoying that anything we pass in also needs to be passed back if we -want to use it again, in addition to any data resulting from the body of the -function that we might want to return as well. +While this works, taking ownership and then returning ownership with every +function is a bit tedious. What if we want to let a function use a value but +not take ownership? It’s quite annoying that anything we pass in also needs to +be passed back if we want to use it again, in addition to any data resulting +from the body of the function that we might want to return as well. -Rust does allow us to return multiple values using a tuple, as shown in Listing 4-5. +Rust does let us return multiple values using a tuple, as shown in Listing 4-5. Filename: src/main.rs @@ -559,19 +567,24 @@ fn calculate_length(s: String) -> (String, usize) { Listing 4-5: Returning ownership of parameters But this is too much ceremony and a lot of work for a concept that should be -common. Luckily for us, Rust has a feature for using a value without transferring ownership, called -*references*. +common. Luckily for us, Rust has a feature for using a value without +transferring ownership, called *references*. ## References and Borrowing The issue with the tuple code in Listing 4-5 is that we have to return the `String` to the calling function so we can still use the `String` after the call to `calculate_length`, because the `String` was moved into -`calculate_length`. Instead, we can provide a reference to XXX, meaning XXX - +`calculate_length`. Instead, we can provide a reference to the `String` value. +A *reference* is like a pointer in that it’s an address we can follow to access +data stored at that address that is owned by some other variable. Unlike a +pointer, a reference is guaranteed to point to a valid value of a particular +type. + + Here is how you would define and use a `calculate_length` function that has a -reference to an object as a parameter instead of taking ownership of the -value: +reference to an object as a parameter instead of taking ownership of the value: Filename: src/main.rs @@ -592,8 +605,8 @@ fn calculate_length(s: &String) -> usize { First, notice that all the tuple code in the variable declaration and the function return value is gone. Second, note that we pass `&s1` into `calculate_length` and, in its definition, we take `&String` rather than -`String`. These ampersands represent *references*, and they allow you to refer to some value -without taking ownership of it. Figure 4-5 depicts this concept. +`String`. These ampersands represent *references*, and they allow you to refer +to some value without taking ownership of it. Figure 4-5 depicts this concept. &String s pointing at String s1 @@ -607,9 +620,9 @@ Figure 4-5: A diagram of `&String s` pointing at `String s1` Let’s take a closer look at the function call here: ``` - let s1 = String::from("hello"); +let s1 = String::from("hello"); - let len = calculate_length(&s1); +let len = calculate_length(&s1); ``` The `&s1` syntax lets us create a reference that *refers* to the value of `s1` @@ -627,14 +640,15 @@ fn calculate_length(s: &String) -> usize { // s is a reference to a String ``` The scope in which the variable `s` is valid is the same as any function -parameter’s scope, but the value pointed to by the reference is not dropped when `s` -stops being used because we don’t have ownership. When functions have -references as parameters instead of the actual values, we won’t need to return -the values in order to give back ownership, because we never had ownership. +parameter’s scope, but the value pointed to by the reference is not dropped +when `s` stops being used because `s` doesn’t have ownership. When functions +have references as parameters instead of the actual values, we won’t need to +return the values in order to give back ownership, because we never had +ownership. We call the action of creating a reference *borrowing*. As in real life, if a person owns something, you can borrow it from them. When you’re done, you have -to give it back. You don't own it. +to give it back. You don’t own it. So what happens if we try to modify something we’re borrowing? Try the code in Listing 4-6. Spoiler alert: it doesn’t work! @@ -658,8 +672,6 @@ Listing 4-6: Attempting to modify a borrowed value Here’s the error: ``` -$ cargo run - Compiling ownership v0.1.0 (file:///projects/ownership) error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference --> src/main.rs:8:5 | @@ -675,7 +687,7 @@ allowed to modify something we have a reference to. ### Mutable References We can fix the code from Listing 4-6 to allow us to modify a borrowed value -with just a few small tweaks that implement, instead, a *mutable reference*: +with just a few small tweaks that use, instead, a *mutable reference*: Filename: src/main.rs @@ -691,15 +703,14 @@ fn change(some_string: &mut String) { } ``` -First, we change `s` to be `mut`. Then we create a mutable -reference with `&mut s` where we call the `change` function, and update the -function signature to accept a mutable reference with `some_string: &mut -String`. This makes it very clear that the `change` function will mutate the -value it borrows. +First, we change `s` to be `mut`. Then we create a mutable reference with `&mut +s` where we call the `change` function, and update the function signature to +accept a mutable reference with `some_string: &mut String`. This makes it very +clear that the `change` function will mutate the value it borrows. Mutable references have one big restriction: you can have only one mutable -reference to a particular piece of data at a time. This code that attempts to create -two mutable references to `s` will fail: +reference to a particular piece of data at a time. This code that attempts to +create two mutable references to `s` will fail: Filename: src/main.rs @@ -715,8 +726,6 @@ Filename: src/main.rs Here’s the error: ``` -$ cargo run - Compiling ownership v0.1.0 (file:///projects/ownership) error[E0499]: cannot borrow `s` as mutable more than once at a time --> src/main.rs:5:14 | @@ -738,9 +747,9 @@ in `r2` that borrows the same data as `r1`. The restriction preventing multiple mutable references to the same data at the same time allows for mutation but in a very controlled fashion. It’s something that new Rustaceans struggle with, because most languages let you mutate -whenever you’d like. The benefit of having this restriction is that Rust can prevent data races at -compile time. A *data race* is similar to a race condition and happens when -these three behaviors occur: +whenever you’d like. The benefit of having this restriction is that Rust can +prevent data races at compile time. A *data race* is similar to a race +condition and happens when these three behaviors occur: * Two or more pointers access the same data at the same time. * At least one of the pointers is being used to write to the data. @@ -754,33 +763,31 @@ As always, we can use curly brackets to create a new scope, allowing for multiple mutable references, just not *simultaneous* ones: ``` - let mut s = String::from("hello"); +let mut s = String::from("hello"); - { - let r1 = &mut s; - } // r1 goes out of scope here, so we can make a new reference with no problems. +{ + let r1 = &mut s; +} // r1 goes out of scope here, so we can make a new reference with no problems. - let r2 = &mut s; +let r2 = &mut s; ``` -Rust enforces a similar rule for combining mutable and immutable references. This code -results in an error: +Rust enforces a similar rule for combining mutable and immutable references. +This code results in an error: ``` - let mut s = String::from("hello"); +let mut s = String::from("hello"); - let r1 = &s; // no problem - let r2 = &s; // no problem - let r3 = &mut s; // BIG PROBLEM +let r1 = &s; // no problem +let r2 = &s; // no problem +let r3 = &mut s; // BIG PROBLEM - println!("{}, {}, and {}", r1, r2, r3); +println!("{}, {}, and {}", r1, r2, r3); ``` Here’s the error: ``` -$ cargo run - Compiling ownership v0.1.0 (file:///projects/ownership) error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable --> src/main.rs:6:14 | @@ -794,12 +801,15 @@ error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immuta | -- immutable borrow later used here ``` -Whew! We *also* cannot have a mutable reference while we have an immutable one. - -Users of an immutable reference don’t expect the values to suddenly change out -from under them! However, multiple immutable references are allowed because no one -who is just reading the data has the ability to affect anyone else’s reading of -the data. +Whew! We *also* cannot have a mutable reference while we have an immutable one +to the same value. + + +Users of an immutable reference don’t expect the value to suddenly change out +from under them! However, multiple immutable references are allowed because no +one who is just reading the data has the ability to affect anyone else’s +reading of the data. Note that a reference’s scope starts from where it is introduced and continues through the last time that reference is used. For instance, this code will @@ -807,15 +817,15 @@ compile because the last usage of the immutable references, the `println!`, occurs before the mutable reference is introduced: ``` - let mut s = String::from("hello"); +let mut s = String::from("hello"); - let r1 = &s; // no problem - let r2 = &s; // no problem - println!("{} and {}", r1, r2); - // variables r1 and r2 will not be used after this point +let r1 = &s; // no problem +let r2 = &s; // no problem +println!("{} and {}", r1, r2); +// variables r1 and r2 will not be used after this point - let r3 = &mut s; // no problem - println!("{}", r3); +let r3 = &mut s; // no problem +println!("{}", r3); ``` The scopes of the immutable references `r1` and `r2` end after the `println!` @@ -841,7 +851,7 @@ never be dangling references: if you have a reference to some data, the compiler will ensure that the data will not go out of scope before the reference to the data does. -Let’s try to create a dangling reference, and see how Rust prevents them with a +Let’s try to create a dangling reference to see how Rust prevents them with a compile-time error: Filename: src/main.rs @@ -861,8 +871,6 @@ fn dangle() -> &String { Here’s the error: ``` -$ cargo run - Compiling ownership v0.1.0 (file:///projects/ownership) error[E0106]: missing lifetime specifier --> src/main.rs:5:16 | @@ -882,7 +890,7 @@ about lifetimes, the message does contain the key to why this code is a problem: ``` this function's return type contains a borrowed value, but there is no value -for it to be borrowed from. +for it to be borrowed from ``` Let’s take a closer look at exactly what’s happening at each stage of our @@ -930,16 +938,17 @@ Next, we’ll look at a different kind of reference: slices. ## The Slice Type -*Slices* let you -reference a contiguous sequence of elements in a collection rather than the -whole collection. Since it's a reference, a slice does not have ownership. +*Slices* let you reference a contiguous sequence of elements in a collection +rather than the whole collection. A slice is a kind of reference, so it does +not have ownership. Here’s a small programming problem: write a function that takes a string and returns the first word it finds in that string. If the function doesn’t find a space in the string, the whole string must be one word, so the entire string should be returned. -Let’s work through how we'd build the signature of this function without using slices, to set up the problem that slices will solve: +Let’s work through how we’d write the signature of this function without using +slices, to understand the problem that slices will solve: ``` fn first_word(s: &String) -> ? @@ -972,7 +981,12 @@ Listing 4-7: The `first_word` function that returns a byte index value into the Because we need to go through the `String` element by element and check whether a value is a space, we’ll convert our `String` to an array of bytes using the `as_bytes` method [1]. - + + Next, we create an iterator over the array of bytes using the `iter` method [3]. We’ll discuss iterators in more detail in Chapter 13. For now, know that `iter` is a method that returns each element in a collection and that `enumerate` @@ -982,9 +996,9 @@ second element is a reference to the element. This is a bit more convenient than calculating the index ourselves. Because the `enumerate` method returns a tuple, we can use patterns to -destructure that tuple. We'll be discussing patterns more in Chapter 6. In -the `for` loop, we specify a pattern that has `i` for the index in the tuple -and `&item` for the single byte in the tuple [2]. Because we get a reference to the +destructure that tuple. We’ll be discussing patterns more in Chapter 6. In the +`for` loop, we specify a pattern that has `i` for the index in the tuple and +`&item` for the single byte in the tuple [2]. Because we get a reference to the element from `.iter().enumerate()`, we use `&` in the pattern. Inside the `for` loop, we search for the byte that represents the space by @@ -1048,16 +1062,15 @@ A *string slice* is a reference to part of a `String`, and it looks like this: let world = &s[6..11]; ``` -Rather than a reference to the entire `String`, it’s a reference -to a portion of the `String`, specified in the extra -`[0..5]` bit. We create slices using a range within brackets by specifying -`[starting_index..ending_index]`, where `starting_index` is the first position -in the slice and `ending_index` is one more than the last position in the -slice. Internally, the slice data structure stores the starting position and -the length of the slice, which corresponds to `ending_index` minus -`starting_index`. So in the case of `let world = &s[6..11];`, `world` would be -a slice that contains a pointer to the byte at index 6 of `s` with a length -value of 5. +Rather than a reference to the entire `String`, `hello` is a reference to a +portion of the `String`, specified in the extra `[0..5]` bit. We create slices +using a range within brackets by specifying `[starting_index..ending_index]`, +where `starting_index` is the first position in the slice and `ending_index` is +one more than the last position in the slice. Internally, the slice data +structure stores the starting position and the length of the slice, which +corresponds to `ending_index` minus `starting_index`. So in the case of `let +world = &s[6..11];`, `world` would be a slice that contains a pointer to the +byte at index 6 of `s` with a length value of 5. Figure 4-6 shows this in a diagram. @@ -1167,9 +1180,8 @@ fn main() { Here’s the compiler error: ``` -$ cargo run - Compiling ownership v0.1.0 (file:///projects/ownership) -error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable +error[E0502]: cannot borrow `s` as mutable because it is also borrowed as +immutable --> src/main.rs:18:5 | 16 | let word = first_word(&s);