mirror of
https://github.com/rust-lang-cn/book-cn.git
synced 2025-01-23 23:50:25 +08:00
Make edits to ownership/borrowing/slices suggested by nostarch
This commit is contained in:
parent
60d445df5c
commit
701855e165
@ -1,6 +1,7 @@
|
||||
# Understanding Ownership
|
||||
|
||||
Ownership is important to understand: it's Rust's most unique feature, and
|
||||
enables Rust to make memory safety guarantees without needing a garbage
|
||||
collector. We’ll also talk about several related features: borrowing, slices,
|
||||
and how Rust lays things out in memory.
|
||||
Ownership is Rust's most unique feature, and enables Rust to make memory safety
|
||||
guarantees without needing a garbage collector. It's therefore 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 things out in memory.
|
||||
|
@ -1,8 +1,7 @@
|
||||
## Ownership
|
||||
|
||||
Rust’s central feature is called *ownership*. It is a feature that is
|
||||
straightforward to explain, but has deep implications for the rest of the
|
||||
language.
|
||||
Rust’s central feature is *ownership*. It is a feature that is straightforward
|
||||
to explain, but has deep implications for the rest of the language.
|
||||
|
||||
All programs have to manage the way they use a computer's memory while running.
|
||||
Some languages have garbage collection, while in others, the programmer has to
|
||||
@ -11,7 +10,7 @@ managed through a system of ownership with a set of rules that the compiler
|
||||
checks at compile-time. You do not pay any run-time cost for any of these
|
||||
features.
|
||||
|
||||
However, because ownership is a new concept for many programmers, it does take
|
||||
Since ownership is a new concept for many programmers, it does take
|
||||
some time to get used to. There is good news, though: the more experienced you
|
||||
become with Rust and the rules of the ownership system, the more you'll be
|
||||
able to naturally develop code that is both safe and efficient. Keep at it!
|
||||
@ -76,23 +75,35 @@ ownership exists can help explain why it works the way it does.
|
||||
|
||||
PROD: END BOX
|
||||
|
||||
### Variable binding scope
|
||||
### Ownership Rules
|
||||
|
||||
First, let's take a look at the rules. Keep these in mind as we go through the
|
||||
examples that will illustrate the rules:
|
||||
|
||||
> 1. Each value in Rust has a variable binding that’s called its *owner*.
|
||||
> 2. There can only be one owner at a time.
|
||||
> 3. When the owner goes out of scope, the value will be `drop()`ped.
|
||||
|
||||
### Variable Binding Scope
|
||||
|
||||
We've walked through an example of a Rust program already in the tutorial
|
||||
chapter. Now that we’re past basic syntax, we won’t include all of the `fn
|
||||
main() {` stuff in examples, so if you’re following along, you will have to put
|
||||
them inside of a `main()` function. This lets our examples be a bit more
|
||||
concise, letting us focus on the actual details rather than boilerplate.
|
||||
the following examples inside of a `main()` function yourself. This lets our
|
||||
examples be a bit more concise, letting us focus on the actual details rather
|
||||
than boilerplate.
|
||||
|
||||
Anyway, here is our first example:
|
||||
As a first example of ownership, we'll look at the *scope* of some variable
|
||||
bindings. A scope is the range within a program for which an item is valid.
|
||||
Let's say we have a variable binding that looks like this:
|
||||
|
||||
```rust
|
||||
let s = "hello";
|
||||
```
|
||||
|
||||
This variable binding refers to a string literal, where the value of the string
|
||||
is hard coded into the text of our program. The binding is valid from the point
|
||||
at which it’s declared until the end of the current _scope_. That is:
|
||||
The variable binding `s` refers to a string literal, where the value of the
|
||||
string is hard coded into the text of our program. The binding is valid from
|
||||
the point at which it’s declared until the end of the current _scope_. That is:
|
||||
|
||||
```rust
|
||||
{ // s is not valid here, it’s not yet declared
|
||||
@ -104,13 +115,13 @@ at which it’s declared until the end of the current _scope_. That is:
|
||||
|
||||
In other words, there are two important points in time here:
|
||||
|
||||
- When `s` comes "into scope", it is valid.
|
||||
- It remains so until it "goes out of scope".
|
||||
- When `s` comes *into scope*, it is valid.
|
||||
- It remains so until it *goes out of scope*.
|
||||
|
||||
At this point, things are similar to other programming languages. Now let’s
|
||||
build on top of this understanding by introducing the `String` type.
|
||||
|
||||
### Strings
|
||||
### The `String` Type
|
||||
|
||||
In order to illustrate the rules of ownership, we need a data type that is more
|
||||
complex than the ones we covered in Chapter 3. All of the data types we've
|
||||
@ -144,7 +155,7 @@ particular `from()` function under the `String` type itself, rather than using
|
||||
some sort of name like `string_from()`. We’ll discuss this syntax more in the
|
||||
“Method Syntax” and “Modules” chapters.
|
||||
|
||||
This kind of string can be mutated:
|
||||
This kind of string *can* be mutated:
|
||||
|
||||
```rust
|
||||
let mut s = String::from("hello");
|
||||
@ -162,8 +173,8 @@ In the case of a string literal, because we know the contents at compile time,
|
||||
the text is hard-coded directly into the final executable and stored with the
|
||||
code on the stack. This makes string literals quite fast and efficient. But
|
||||
these properties only come from its immutability. Unfortunately, we can’t put a
|
||||
blob of memory into the binary for each string whose size is unknown at compile
|
||||
time and whose size might change over the course of running the program.
|
||||
blob of memory into the binary for each piece of text whose size is unknown at
|
||||
compile time and whose size might change over the course of running the program.
|
||||
|
||||
With the `String` type, in order to support a mutable, growable piece of text,
|
||||
we need to allocate an amount of memory on the heap, unknown at compile time,
|
||||
@ -178,18 +189,18 @@ implementation requests the memory it needs. This is pretty much universal in
|
||||
programming languages.
|
||||
|
||||
The second case, however, is different. In languages with a *garbage collector*
|
||||
(*GC*), the GC will keep track and clean up memory that isn't being used
|
||||
anymore, and we, as the programmer, don’t need to think about it. Without GC,
|
||||
it’s our responsibility to identify when memory is no longer being used and
|
||||
(GC), the GC will keep track and clean up memory that isn't being used anymore,
|
||||
and we, as the programmer, don’t need to think about it. Without GC, it’s the
|
||||
programmer's 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 problem. If we forget, we will
|
||||
waste memory. If we do it too early, we will 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()`.
|
||||
correctly has historically been a difficult problem in programming. If we
|
||||
forget, we will waste memory. If we do it too early, we will 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. Remember our example? Here’s a version with
|
||||
`String`:
|
||||
Rust takes a different path: the memory is automatically returned once the
|
||||
binding to it goes out of scope. Here’s a version of our scope example from
|
||||
earlier using `String`:
|
||||
|
||||
```rust
|
||||
{
|
||||
@ -199,15 +210,15 @@ Rust takes a different path. Remember our example? Here’s a version with
|
||||
} // this scope is now over, and s is no longer valid
|
||||
```
|
||||
|
||||
We have a natural point at which we can return the memory our `String` needs
|
||||
back to the operating system: when it goes out of scope. When a variable goes
|
||||
out of scope, Rust calls a special function for us. This function is called
|
||||
`drop()`, and it is where the author of `String` can put the code to return the
|
||||
memory.
|
||||
There is a natural point at which we can return the memory our `String` needs
|
||||
back to the operating system: when `s` goes out of scope. When a variable
|
||||
binding goes out of scope, Rust calls a special function for us. This function
|
||||
is called `drop()`, and it is where the author of `String` can put the code to
|
||||
return the memory. Rust calls `drop()` automatically at the closing `}`.
|
||||
|
||||
> Aside: This pattern is sometimes called “Resource Acquisition Is
|
||||
> Note: This pattern is sometimes called “Resource Acquisition Is
|
||||
> Initialization” in C++, or “RAII” for short. While they are very similar,
|
||||
> Rust’s take on this concept has a number of differences, and so we don’t tend
|
||||
> Rust’s take on this concept has a number of differences, so we don’t tend
|
||||
> to use the same term. If you’re familiar with this idea, keep in mind that it
|
||||
> is _roughly_ similar in Rust, but not identical.
|
||||
|
||||
@ -216,9 +227,10 @@ seem simple right now, but things can get tricky in more advanced situations
|
||||
when we want to have multiple variable bindings use the data that we have
|
||||
allocated on the heap. Let’s go over some of those situations now.
|
||||
|
||||
### Move
|
||||
#### Ways Bindings and Data Interact: Move
|
||||
|
||||
What would you expect this code to do?
|
||||
There are different ways that multiple bindings can interact with the same data
|
||||
in Rust. Let's take an example using an integer:
|
||||
|
||||
```rust
|
||||
let x = 5;
|
||||
@ -231,7 +243,7 @@ bind it to `y`”. We now have two bindings, `x` and `y`, and both equal `5`.
|
||||
This is indeed what is happening since integers are simple values with a known,
|
||||
fixed size, and these two `5` values are pushed onto the stack.
|
||||
|
||||
Now let’s look at `String`. What would you expect this code to do?
|
||||
Now let’s look at the `String` version:
|
||||
|
||||
```rust
|
||||
let s1 = String::from("hello");
|
||||
@ -252,13 +264,14 @@ length, and a capacity. This group of data is stored on the stack. On the right
|
||||
is the memory that holds the contents, and this is on the heap.
|
||||
|
||||
<img alt="String in memory" src="img/trpl04-01.svg" class="center" style="width: 50%;" />
|
||||
Figure 4-1: Representation in memory of a `String` holding the value "hello"
|
||||
bound to `s1`
|
||||
|
||||
A `String` is made up of three parts: a pointer to the memory that holds the
|
||||
contents of the string, a length, and a capacity. The length is how much memory
|
||||
the `String` is currently using. The capacity is the total amount of memory the
|
||||
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
|
||||
`String` has gotten from the operating system. The difference between length
|
||||
and capacity matters but not in this context, so don’t worry about it too much.
|
||||
For right now, it's fine to ignore the capacity.
|
||||
and capacity matters but not in this context, so for now, it's fine to ignore
|
||||
the capacity.
|
||||
|
||||
When we assign `s1` to `s2`, the `String` data itself is copied, meaning we
|
||||
copy the pointer, the length, and the capacity that are on the stack. We do not
|
||||
@ -266,6 +279,8 @@ copy the data on the heap that the `String`'s pointer refers to. In other
|
||||
words, it looks like figure 4-2.
|
||||
|
||||
<img alt="s1 and s2 pointing to the same value" src="img/trpl04-02.svg" class="center" style="width: 50%;" />
|
||||
Figure 4-2: Representation in memory of the binding `s2` that has a copy of
|
||||
`s1`'s pointer, length and capacity
|
||||
|
||||
And _not_ Figure 4-3, which is what memory would look like if Rust instead
|
||||
copied the heap data as well. If Rust did this, the operation `s2 = s1` could
|
||||
@ -311,35 +326,28 @@ println!("{}", s1);
|
||||
|
||||
If you have 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 sounded like a shallow copy. Because Rust
|
||||
without copying the data probably sounds like a shallow copy. But because Rust
|
||||
also invalidates the first binding, instead of calling this a shallow copy,
|
||||
it's called a _move_. Here we would read this by saying that `s1` was _moved_
|
||||
into `s2`. So what actually happens looks like this:
|
||||
it's known as a _move_. Here we would read this by saying that `s1` was _moved_
|
||||
into `s2`. So what actually happens looks like Figure 4-4.
|
||||
|
||||
<img alt="s1 moved to s2" src="img/trpl04-04.svg" class="center" style="width: 50%;" />
|
||||
Figure 4-4: Representation in memory after `s1` has been invalidated
|
||||
|
||||
That solves our problem! With only `s2` valid, when it goes out of scope, it
|
||||
alone will free the memory, and we’re done.
|
||||
|
||||
### Ownership Rules
|
||||
|
||||
This leads us to the Ownership Rules:
|
||||
|
||||
> 1. Each value in Rust has a variable binding that’s called its *owner*.
|
||||
> 2. There can only be one owner at a time.
|
||||
> 3. When the owner goes out of scope, the value will be `drop()`ped.
|
||||
|
||||
Furthermore, there’s a design choice that’s implied by this: Rust will never
|
||||
automatically create "deep" copies of your data. Therefore, any _automatic_
|
||||
copying can be assumed to be inexpensive.
|
||||
|
||||
### Clone
|
||||
#### Ways Bindings and Data Interact: Clone
|
||||
|
||||
But what if we _do_ want to deeply copy the `String`’s data and not just the
|
||||
`String` itself? There’s a common method for that: `clone()`. We will discuss
|
||||
methods in the section on [`structs` in Chapter XX][structs]<!-- ignore -->,
|
||||
but they’re a common enough feature in many programming languages that you have
|
||||
probably seen them before.
|
||||
If we _do_ want to deeply copy the `String`’s data and not just the `String`
|
||||
itself, there’s a common method for that: `clone()`. We will discuss methods in
|
||||
the section on [`structs` in Chapter XX][structs]<!-- ignore -->, but they’re a
|
||||
common enough feature in many programming languages that you have probably seen
|
||||
them before.
|
||||
|
||||
[structs]: ch05-01-structs.html
|
||||
|
||||
@ -349,7 +357,7 @@ Here’s an example of the `clone()` method in action:
|
||||
let s1 = String::from("hello");
|
||||
let s2 = s1.clone();
|
||||
|
||||
println!("{}", s1);
|
||||
println!("s1 = {}, s2 = {}", s1, s2);
|
||||
```
|
||||
|
||||
This will work just fine, and this is how you can explicitly get the behavior
|
||||
@ -361,13 +369,14 @@ different is going on here.
|
||||
|
||||
#### Stack-only Data: Copy
|
||||
|
||||
There’s one last wrinkle that we haven’t talked about yet. This code works:
|
||||
There’s another wrinkle we haven’t talked about yet. This code, that we showed
|
||||
earlier, works and is valid:
|
||||
|
||||
```rust
|
||||
let x = 5;
|
||||
let y = x;
|
||||
|
||||
println!("{}", x);
|
||||
println!("x = {}, y = {}", x, y);
|
||||
```
|
||||
|
||||
This seems to contradict what we just learned: we don't have a call to
|
||||
@ -391,7 +400,8 @@ we will get a compile-time error.
|
||||
|
||||
So what types are `Copy`? You can check the documentation for the given type to
|
||||
be sure, but as a rule of thumb, any group of simple scalar values can be Copy,
|
||||
but nothing that requires allocation or is some form of resource is `Copy`. Here’s some of the types that are `Copy`:
|
||||
and nothing that requires allocation or is some form of resource is `Copy`.
|
||||
Here’s some of the types that are `Copy`:
|
||||
|
||||
* All of the integer types, like `u32`.
|
||||
* The booleans, `true` and `false`.
|
||||
@ -399,52 +409,29 @@ but nothing that requires allocation or is some form of resource is `Copy`. Here
|
||||
* Tuples, but only if they contain types which are also `Copy`. `(i32, i32)`
|
||||
is `Copy`, but `(i32, String)` is not.
|
||||
|
||||
### Ownership and functions
|
||||
### Ownership and Functions
|
||||
|
||||
Passing a value to a function has similar semantics as assigning it:
|
||||
The semantics for passing a value to a function are similar to assigning a
|
||||
value to a binding. Passing a binding to a function will move or copy, just
|
||||
like assignment. Here’s an example, with some annotations showing where bindings
|
||||
go into and out of scope:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let s = String::from("hello");
|
||||
let s = String::from("hello"); // s comes into scope.
|
||||
|
||||
takes_ownership(s);
|
||||
|
||||
let x = 5;
|
||||
|
||||
makes_copy(x);
|
||||
}
|
||||
|
||||
fn takes_ownership(some_string: String) {
|
||||
println!("{}", some_string);
|
||||
}
|
||||
|
||||
fn makes_copy(some_integer: i32) {
|
||||
println!("{}", some_integer);
|
||||
}
|
||||
```
|
||||
|
||||
Passing a binding to a function will move or copy, just like assignment. Here’s
|
||||
the same example, but with some annotations showing where things go into and
|
||||
out of scope:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let s = String::from("hello"); // s goes into scope.
|
||||
|
||||
takes_ownership(s); // s moves into the function...
|
||||
takes_ownership(s); // s's value moves into the function...
|
||||
// ... and so is no longer valid here.
|
||||
let x = 5; // x goes into scope.
|
||||
let x = 5; // x comes into scope.
|
||||
|
||||
makes_copy(x); // x would move into the function,
|
||||
// but i32 is Copy, so it’s okay to still
|
||||
// use x afterward.
|
||||
|
||||
} // Here, x goes out of scope, then s. But since s was moved, nothing special
|
||||
// happens.
|
||||
} // Here, x goes out of scope, then s. But since s's value was moved, nothing
|
||||
// special happens.
|
||||
|
||||
fn takes_ownership(some_string: String) { // some_string comes into scope.
|
||||
println!("{}", some_string);
|
||||
@ -456,37 +443,14 @@ fn makes_copy(some_integer: i32) { // some_integer comes into scope.
|
||||
} // Here, some_integer goes out of scope. Nothing special happens.
|
||||
```
|
||||
|
||||
Remember: If we tried to use `s` after the call to `takes_ownership()`, Rust
|
||||
If we tried to use `s` after the call to `takes_ownership()`, Rust
|
||||
would throw a compile-time error. These static checks protect us from mistakes.
|
||||
Try adding code to `main` that uses `s` and `x` to see where you can use them
|
||||
and where the ownership rules prevent you from doing so.
|
||||
|
||||
Returning values can also transfer ownership:
|
||||
### Return Values and Scope
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let s1 = gives_ownership();
|
||||
|
||||
let s2 = String::from("hello");
|
||||
|
||||
let s3 = takes_and_gives_back(s2);
|
||||
}
|
||||
|
||||
fn gives_ownership() -> String {
|
||||
let some_string = String::from("hello");
|
||||
|
||||
some_string
|
||||
}
|
||||
|
||||
fn takes_and_gives_back(a_string: String) -> String {
|
||||
|
||||
a_string
|
||||
}
|
||||
```
|
||||
|
||||
With similiar annotations:
|
||||
Returning values can also transfer ownership. Here's an example with similar annotations:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
@ -521,14 +485,18 @@ fn takes_and_gives_back(a_string: String) -> String { // a_string comes into sco
|
||||
}
|
||||
```
|
||||
|
||||
It’s the same pattern, every time: assigning something moves it, and when an
|
||||
owner goes out of scope, if it hasn’t been moved, it will `drop()`.
|
||||
It’s the same pattern, every time: assigning a value to another binding moves
|
||||
it, and when heap data values' bindings go out of scope, if the data hasn’t
|
||||
been moved to be owned by another binding, the value will be cleaned up by
|
||||
`drop()`.
|
||||
|
||||
This might seem a bit tedious, and it is. 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. It's _possible_ to return multiple values, using a tuple, like this:
|
||||
Taking ownership 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.
|
||||
|
||||
It is possible to return multiple values using a tuple, like this:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
@ -549,5 +517,4 @@ fn calculate_length(s: String) -> (String, usize) {
|
||||
```
|
||||
|
||||
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 this concept, and it’s what the
|
||||
next section is about.
|
||||
common. Luckily for us, Rust has a feature for this concept: references.
|
||||
|
@ -1,31 +1,12 @@
|
||||
## References and Borrowing
|
||||
|
||||
At the end of the last section, we had some example Rust that wasn’t very
|
||||
good. Here it is again:
|
||||
The issue with the tuple code at the end of the last section is that we have to
|
||||
return the `String` back to the calling function so that we can still use the
|
||||
`String` after the call to `calculate_length()`, since the `String` was moved
|
||||
into `calculate_length()`.
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let s1 = String::from("hello");
|
||||
|
||||
let (s2, len) = calculate_length(s1);
|
||||
|
||||
println!("The length of '{}' is {}.", s2, len);
|
||||
}
|
||||
|
||||
fn calculate_length(s: String) -> (String, usize) {
|
||||
let length = s.len(); // len() returns the length of a String.
|
||||
|
||||
(s, length)
|
||||
}
|
||||
```
|
||||
|
||||
The issue here is that we have to return the `String` back to the calling
|
||||
function so that we can still use it there, since it was moved when we called
|
||||
`calculate_length()`.
|
||||
|
||||
There is a better way. It looks like this:
|
||||
Here is how you would use a function without taking ownership of it using
|
||||
*references:*
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
@ -50,10 +31,11 @@ function return value is gone. Next, note that we pass `&s1` into
|
||||
`calculate_length()`, and in its definition, we take `&String` rather than
|
||||
`String`.
|
||||
|
||||
These `&`s are called *references*, and they allow you to refer to some value
|
||||
without taking ownership of it. Here’s a diagram:
|
||||
These `&`s are *references*, and they allow you to refer to some value
|
||||
without taking ownership of it. Figure 4-5 shows a diagram of this.
|
||||
|
||||
<img alt="&String s pointing at String s1" src="img/trpl04-05.svg" class="center" />
|
||||
Figure 4-5: `&String s` pointing at `String s1`
|
||||
|
||||
Let’s take a closer look at the function call here:
|
||||
|
||||
@ -68,9 +50,9 @@ let s1 = String::from("hello");
|
||||
let len = calculate_length(&s1);
|
||||
```
|
||||
|
||||
The `&s1` syntax lets us create a reference with `s1`. This reference _refers_
|
||||
to the value of `s1` but does not own it. Because it does not own it, the
|
||||
value it points to will not be dropped when the reference goes out of scope.
|
||||
The `&s1` syntax lets us create a reference which _refers_ to the value of `s1`
|
||||
but does not own it. Because it does not own it, the value it points to will
|
||||
not be dropped when the reference goes out of scope.
|
||||
|
||||
Likewise, the signature of the function uses `&` to indicate that it takes
|
||||
a reference as an argument. Let’s add some explanatory annotations:
|
||||
@ -84,17 +66,17 @@ fn calculate_length(s: &String) -> usize { // s is a reference to a String
|
||||
// it refers to, nothing happens.
|
||||
```
|
||||
|
||||
It’s the same process as before, except that because we don’t have ownership,
|
||||
we don’t drop what a reference points to when the reference goes out of scope.
|
||||
This lets us write functions which take references as arguments instead of the
|
||||
values themselves, so that we won’t need to return them to give back ownership.
|
||||
It’s the same process as before, but we don’t drop what the reference points to
|
||||
when it goes out of scope because we don't have ownership. This lets us write
|
||||
functions which take references as arguments instead of the values themselves,
|
||||
so that we won’t need to return them to give back ownership.
|
||||
|
||||
There’s another word for what references do, and that’s *borrowing*. Just like
|
||||
with real life, if a person owns something, you can borrow it from them. When
|
||||
you’re done, you have to give it back.
|
||||
We call this process *borrowing*. Just like with real life, if a person owns
|
||||
something, you can borrow it from them, and when you’re done, you have to give
|
||||
it back.
|
||||
|
||||
Speaking of which, what if you try to modify something you borrow from me? Try
|
||||
this code out. Spoiler alert: it doesn’t work!
|
||||
So what happens if we try to modify something we're borrowing? Try this code
|
||||
out. Spoiler alert: it doesn’t work!
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
@ -120,12 +102,12 @@ error: cannot borrow immutable borrowed content `*some_string` as mutable
|
||||
| ^^^^^^^^^^^
|
||||
```
|
||||
|
||||
Just like bindings are immutable by default, so are references. We’re not
|
||||
allowed to modify something we have a reference to.
|
||||
Just as bindings are immutable by default, so are references. We’re not allowed
|
||||
to modify something we have a reference to.
|
||||
|
||||
### Mutable references
|
||||
### Mutable References
|
||||
|
||||
We can fix this bug! Just a small tweak:
|
||||
We can fix this error with just a small tweak:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
@ -145,7 +127,9 @@ First, we had to change `s` to be `mut`. Then we had to create a mutable
|
||||
reference with `&mut s` and accept a mutable reference with `some_string: &mut
|
||||
String`.
|
||||
|
||||
Mutable references have one big restriction, though. This code fails:
|
||||
Mutable references have one big restriction, though: you can only have one
|
||||
mutable reference to a particular piece of data in a particular scope. This
|
||||
code will fail:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
@ -170,10 +154,15 @@ error[E0499]: cannot borrow `s` as mutable more than once at a time
|
||||
| - first borrow ends here
|
||||
```
|
||||
|
||||
The error is what it says: you cannot borrow something mutably more than once
|
||||
at a time. This restriction allows for mutation but in a very controlled
|
||||
fashion. It is something that new Rustaceans struggle with, because most
|
||||
languages let you mutate whenever you’d like.
|
||||
This restriction allows for mutation but in a very controlled fashion. It is
|
||||
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 a particular type of
|
||||
race condition where two or more pointers access the same data at the same
|
||||
time, and at least one of the pointers is being used to write to the data. Data
|
||||
races cause unpredictable behavior and can be difficult to diagnose and fix
|
||||
when trying to track them down at runtime; Rust prevents this problem from
|
||||
happening since it won't even compile code with data races!
|
||||
|
||||
As always, we can use `{}`s to create a new scope, allowing for multiple mutable
|
||||
references, just not _simultaneous_ ones:
|
||||
@ -217,15 +206,22 @@ error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immuta
|
||||
|
||||
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! Multiple immutable references are okay, however.
|
||||
from under them! Multiple immutable references are okay, however, since no one
|
||||
who is just reading the data has the ability to affect anyone else's reading of
|
||||
the data.
|
||||
|
||||
### Dangling references
|
||||
Even though these errors may be frustrating at times, remember that it's the
|
||||
Rust compiler pointing out a potential bug earlier (at compile time rather than
|
||||
at runtime) and showing you exactly where the problem is instead of you having
|
||||
to track down why sometimes your data isn't what you thought it should be.
|
||||
|
||||
In languages with pointers, it’s easy to create a “dangling pointer” by freeing
|
||||
some memory while keeping around a pointer to that memory. In Rust, by
|
||||
contrast, the compiler guarantees that references will never be dangling: if we
|
||||
have a reference to something, the compiler will ensure that it will not go
|
||||
out of scope before the reference does.
|
||||
### Dangling References
|
||||
|
||||
In languages with pointers, it's easy to make the error of creating a “dangling
|
||||
pointer” by freeing some memory while keeping around a pointer to that memory.
|
||||
In Rust, by contrast, the compiler guarantees that references will never be
|
||||
dangling: if we 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:
|
||||
|
||||
@ -259,16 +255,14 @@ error[E0106]: missing lifetime specifier
|
||||
error: aborting due to previous error
|
||||
```
|
||||
|
||||
This error message refers to a feature we haven’t learned about yet,
|
||||
*lifetimes*. The message does contain the key to why this code is a problem,
|
||||
though:
|
||||
This error message refers to a feature we haven’t learned about yet:
|
||||
*lifetimes*. We'll discuss lifetimes in detail in Chapter XX, but, disregarding
|
||||
the parts 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`.
|
||||
|
||||
```bash
|
||||
this function’s return type contains a borrowed value, but there is no value
|
||||
for it to be borrowed from
|
||||
```
|
||||
|
||||
Let’s examine exactly what happens with `dangle()`:
|
||||
Let’s have a closer look at exactly what's happenening at each stage of our
|
||||
`dangle()` code:
|
||||
|
||||
```rust,ignore
|
||||
fn dangle() -> &String { // dangle returns a reference to a String
|
||||
|
@ -1,13 +1,13 @@
|
||||
## Slices
|
||||
|
||||
So far, we’ve talked about types that have ownership, like `String`, and ones
|
||||
that don’t, like `&String`. There is another kind of type which does not have
|
||||
ownership: slices. Slices let you reference a contiguous sequence of elements
|
||||
in a collection rather than the whole collection itself.
|
||||
There is another data type which does not have ownership: slices. Slices let
|
||||
you reference a contiguous sequence of elements in a collection rather than the
|
||||
whole collection itself.
|
||||
|
||||
Here’s a small programming problem: write a function which takes a string
|
||||
and returns the first word you find. If we don’t find a space in the string,
|
||||
then the whole string is a word, so the whole thing should be returned.
|
||||
Here’s a small programming problem: write a function which takes a string and
|
||||
returns the first word it finds in that string. If it doesn’t find a space in
|
||||
the string, it means the whole string is one word, so the whole thing should be
|
||||
returned.
|
||||
|
||||
Let’s think about the signature of this function:
|
||||
|
||||
@ -50,16 +50,16 @@ array of bytes using the `.as_bytes()` method.
|
||||
for (i, &item) in bytes.iter().enumerate() {
|
||||
```
|
||||
|
||||
We will be discussing iterators in more detail in Chapter XX, but for
|
||||
now, know that `iter()` is a method that returns each element in a
|
||||
collection, and `enumerate()` modifies the result of `iter()` and returns
|
||||
a tuple instead. The first element of the tuple is the index, and the
|
||||
second element is a reference to the element itself. This is a bit
|
||||
nicer than calculating the index ourselves.
|
||||
We will be discussing iterators in more detail in Chapter XX, but for now, know
|
||||
that `iter()` is a method that returns each element in a collection, and
|
||||
`enumerate()` modifies the result of `iter()` and returns each element as part
|
||||
of a tuple instead, where the first element of the tuple is the index, and the
|
||||
second element is a reference to the element itself. This is a bit nicer than
|
||||
calculating the index ourselves.
|
||||
|
||||
Since it’s a tuple, we can use patterns, just like elsewhere in Rust. So we
|
||||
match against the tuple with i for the index and &item for a single byte. Since
|
||||
we get a reference from `.iter().enumerate()`, we use `&` in the pattern.
|
||||
match against the tuple with `i` for the index and `&item` for a single byte.
|
||||
Since we get a reference from `.iter().enumerate()`, we use `&` in the pattern.
|
||||
|
||||
```rust,ignore
|
||||
if item == b' ' {
|
||||
@ -70,13 +70,15 @@ s.len()
|
||||
```
|
||||
|
||||
We search for the byte that represents the space, using the byte literal
|
||||
syntax. If we find one, we return the position. Otherwise, we return the length
|
||||
of the string, using `s.len()`.
|
||||
syntax. If we find a space, we return the position. Otherwise, we return the
|
||||
length of the string, using `s.len()`.
|
||||
|
||||
This works, but there’s a problem. We’re returning a `usize` on its own, but
|
||||
it’s only a meaningful number in the context of the `&String`. In other
|
||||
words, because it’s a separate value from the `String`, there’s no guarantee
|
||||
that it will still be valid in the future. Consider this:
|
||||
We now have a way to find out the index of the end of the first word in the
|
||||
string, but there’s a problem. We’re returning a `usize` on its own, but it’s
|
||||
only a meaningful number in the context of the `&String`. In other words,
|
||||
because it’s a separate value from the `String`, there’s no guarantee that it
|
||||
will still be valid in the future. Consider this program that uses this
|
||||
`first_word()` function:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
@ -105,6 +107,12 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
This program compiles without any errors, and also would if we used `word`
|
||||
after calling `s.clear()`. `word` isn't connected to the state of `s` at all,
|
||||
so `word` still contains the value `5`. We could use that `5` with `s` to try
|
||||
to extract the first word out, but this would be a bug since the contents of
|
||||
`s` have changed since we saved `5` in `word`.
|
||||
|
||||
This is bad! It’s even worse if we wanted to write a `second_word()`
|
||||
function. Its signature would have to look like this:
|
||||
|
||||
@ -112,15 +120,16 @@ function. Its signature would have to look like this:
|
||||
fn second_word(s: &String) -> (usize, usize) {
|
||||
```
|
||||
|
||||
Now we’re tracking both a start _and_ an ending index. Even more chances for
|
||||
things to go wrong. We now have three unrelated variable bindings floating
|
||||
Now we’re tracking both a start _and_ an ending index, and we have even more
|
||||
values that were calculated from data in a particular state but aren't tied to
|
||||
that state at all. We now have three unrelated variable bindings floating
|
||||
around which need to be kept in sync.
|
||||
|
||||
Luckily, Rust has a solution to this problem: string slices.
|
||||
|
||||
## String slices
|
||||
### String Slices
|
||||
|
||||
A string slice looks like this:
|
||||
A string slice is a reference to part of a `String`, and looks like this:
|
||||
|
||||
```rust
|
||||
let s = String::from("hello world");
|
||||
@ -129,19 +138,20 @@ let hello = &s[0..5];
|
||||
let world = &s[6..11];
|
||||
```
|
||||
|
||||
This looks just like taking a reference to the whole `String`, but with the
|
||||
extra `[0..5]` bit. Instead of being a reference to the entire `String`, it’s a
|
||||
This is similar to taking a reference to the whole `String`, but with the
|
||||
extra `[0..5]` bit. Rather than a reference to the entire `String`, it’s a
|
||||
reference to an internal position in the `String` and the number of elements
|
||||
that it refers to.
|
||||
|
||||
We can create slices with a range of `[starting_index..ending_index]`, but the
|
||||
We create slices with a range of `[starting_index..ending_index]`, but the
|
||||
slice data structure actually stores the starting position and the length of the
|
||||
slice. So in the case of `let world = &s[6..11];`, `world` would be a slice that
|
||||
contains a pointer to the 6th byte of `s` and a length value of 5.
|
||||
|
||||
In other words, it looks like this:
|
||||
Figure 4-7 shows this in a diagram:
|
||||
|
||||
<img alt="world containing a pointer to the 6th byte of String s and a length 5" src="img/trpl04-06.svg" class="center" style="width: 50%;" />
|
||||
Figure 4-7: Two string slices referring to parts of a `String`
|
||||
|
||||
With Rust’s `..` range syntax, if you want to start at the first index (zero),
|
||||
you can drop the value before the `..`. In other words, these are equal:
|
||||
@ -178,7 +188,8 @@ let slice = &s[0..len];
|
||||
let slice = &s[..];
|
||||
```
|
||||
|
||||
With this in mind, let’s re-write `first_word()` to return a slice:
|
||||
With this in mind, let’s re-write `first_word()` to return a slice. The type
|
||||
that signifies "string slice" is written as `&str`:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
@ -196,20 +207,28 @@ fn first_word(s: &String) -> &str {
|
||||
}
|
||||
```
|
||||
|
||||
Now we have a single value, the `&str`, pronounced "string slice". It stores
|
||||
both elements that we care about: a reference to the starting point of the
|
||||
slice and the number of elements in the slice.
|
||||
We get the index for the end of the word in the same way as before, by looking
|
||||
for the first occurrence of a space. When we find a space, we return a string
|
||||
slice using the start of the string and the index of the space as the starting
|
||||
and ending indices.
|
||||
|
||||
This would also work for a `second_word()`:
|
||||
Now when we call `first_word()`, we get back a single value that is tied to the
|
||||
underlying data. The value is made up of a reference to the starting point of
|
||||
the slice and the number of elements in the slice.
|
||||
|
||||
Returning a slice would also work for a `second_word()` function:
|
||||
|
||||
```rust,ignore
|
||||
fn second_word(s: &String) -> &str {
|
||||
```
|
||||
|
||||
We now have a straightforward API that’s much harder to mess up.
|
||||
|
||||
But what about our error condition from before? Slices also fix that. Using
|
||||
the slice version of `first_word()` will throw an error:
|
||||
We now have a straightforward API that’s much harder to mess up. Remember our
|
||||
bug from before, when we got the first word but then cleared the string so that
|
||||
our first word was invalid? That code was logically incorrect but didn't show
|
||||
any immediate errors-- the problems would show up later, if we kept trying to
|
||||
use the first word index with an emptied string. Slices make this bug
|
||||
impossible, and let us know we have a problem with our code much sooner. Using
|
||||
the slice version of `first_word()` will throw a compile time error:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
@ -223,7 +242,7 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
Here’s the error:
|
||||
Here’s the compiler error:
|
||||
|
||||
```bash
|
||||
17:6 error: cannot borrow `s` as mutable because it is also borrowed as
|
||||
@ -241,13 +260,13 @@ fn main() {
|
||||
^
|
||||
```
|
||||
|
||||
Remember the borrowing rules? If we have an immutable reference to something,
|
||||
we cannot also take a mutable reference. Since `clear()` needs to truncate the
|
||||
`String`, it tries to take a mutable reference, which fails. Not only has Rust
|
||||
made our API easier to use, but it’s also eliminated an entire class of errors
|
||||
at compile time!
|
||||
Remember from the borrowing rules that if we have an immutable reference to
|
||||
something, we cannot also take a mutable reference. Since `clear()` needs to
|
||||
truncate the `String`, it tries to take a mutable reference, which fails. Not
|
||||
only has Rust made our API easier to use, but it’s also eliminated an entire
|
||||
class of errors at compile time!
|
||||
|
||||
### String literals are slices
|
||||
#### String Literals are Slices
|
||||
|
||||
Remember how we talked about string literals being stored inside of the binary
|
||||
itself? Now that we know about slices, we can now properly understand string
|
||||
@ -261,7 +280,7 @@ The type of `s` here is `&str`: It’s a slice, pointing to that specific point
|
||||
of the binary. This is also why string literals are immutable; `&str` is an
|
||||
immutable reference.
|
||||
|
||||
### String slices as arguments
|
||||
#### String Slices as Arguments
|
||||
|
||||
Knowing that you can take slices of both literals and `String`s leads us to
|
||||
one more improvement on `first_word()`, and that’s its signature:
|
||||
@ -270,17 +289,16 @@ one more improvement on `first_word()`, and that’s its signature:
|
||||
fn first_word(s: &String) -> &str {
|
||||
```
|
||||
|
||||
A more experienced Rustacean would write this one instead:
|
||||
A more experienced Rustacean would write this one instead because it allows us
|
||||
to use the same function on both `String`s and `&str`s:
|
||||
|
||||
```rust,ignore
|
||||
fn first_word(s: &str) -> &str {
|
||||
```
|
||||
|
||||
Why is this? Well, we aren’t trying to modify `s` at all. And we can take
|
||||
a string slice that’s the full length of a `String`, so we haven’t lost
|
||||
the ability to talk about full `String`s. And additionally, we can take
|
||||
string slices of string literals too, so this function is more useful, but
|
||||
with no loss of functionality:
|
||||
If we have a string slice, we can pass that as the argument directly. If we
|
||||
have a `String`, we can pass a slice of the entire `String`. This makes our API
|
||||
more general and useful without losing any functionality:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
@ -313,17 +331,17 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
## Other slices
|
||||
### Other Slices
|
||||
|
||||
String slices, as you might imagine, are specific to strings. But there’s a more
|
||||
general slice type, too. Consider arrays:
|
||||
general slice type, too. Consider this array:
|
||||
|
||||
```rust
|
||||
let a = [1, 2, 3, 4, 5];
|
||||
```
|
||||
|
||||
Just like we may want to refer to a part of a string, we may want to refer to
|
||||
part of an array:
|
||||
part of an array, and would do so like this:
|
||||
|
||||
```rust
|
||||
let a = [1, 2, 3, 4, 5];
|
||||
@ -335,3 +353,16 @@ This slice has the type `&[i32]`. It works the exact same way as string slices
|
||||
do, by storing a reference to the first element and a length. You’ll use this
|
||||
kind of slice for all sorts of other collections. We’ll discuss these in detail
|
||||
when we talk about vectors in Chapter XX.
|
||||
|
||||
## Summary
|
||||
|
||||
The concepts of ownership, borrowing, and slices are what ensure memory safety
|
||||
in Rust programs at compile time. Rust is a language that gives you control
|
||||
over your memory usage like other systems programming languages, but having the
|
||||
owner of data automatically clean up that data when the owner goes out of scope
|
||||
means you don't have to write and debug extra code to get this control.
|
||||
|
||||
Ownership affects how lots of other parts of Rust work, so we will be talking
|
||||
about these concepts further throughout the rest of the book. Let's move on to
|
||||
the next chapter where we'll look at grouping pieces of data together in a
|
||||
`struct`.
|
||||
|
Loading…
Reference in New Issue
Block a user