引用与借用
References and Borrowing
示例 4-5 中元组代码的问题在于,我们必须将 String 返回给调用函数,以便在调用 calculate_length 之后仍能使用该 String,因为 String 已被移动到了 calculate_length 中。相反,我们可以提供对 String 值的“引用”(reference)。引用类似于指针,因为它是一个地址,我们可以跟随该地址访问存储在该地址的数据;该数据由其他某个变量所有。与指针不同,引用保证在引用的生命周期内指向特定类型的有效值。
The issue with the tuple code in Listing 4-5 is that we have to return the
String to the calling function so that 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 the String value.
A reference is like a pointer in that it’s an address we can follow to access
the data stored at that address; that data is owned by some other variable.
Unlike a pointer, a reference is guaranteed to point to a valid value of a
particular type for the life of that reference.
下面是你如何定义和使用一个 calculate_length 函数,该函数将对象的引用作为参数,而不是获取值的所有权:
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:
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{s1}' is {len}.");
}
fn calculate_length(s: &String) -> usize {
s.len()
}
首先,请注意变量声明和函数返回值中的所有元组代码都消失了。其次,请注意我们将 &s1 传递给 calculate_length,并且在其定义中,我们接收 &String 而不是 String。这些 & 符号代表“引用”,它们允许你引用某个值而不获取其所有权。图 4-6 描绘了这个概念。
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-6 depicts this concept.
图 4-6:&String s 指向 String s1 的图解
Figure 4-6: A diagram of &String s pointing at
String s1
注意:使用
&进行引用的相反操作是“解引用”(dereferencing),它是通过解引用运算符*完成的。我们将在第 8 章看到解引用运算符的一些用法,并在第 15 章讨论解引用的细节。Note: The opposite of referencing by using
&is dereferencing, which is accomplished with the dereference operator,*. We’ll see some uses of the dereference operator in Chapter 8 and discuss details of dereferencing in Chapter 15.
让我们仔细看看这里的函数调用:
Let’s take a closer look at the function call here:
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{s1}' is {len}.");
}
fn calculate_length(s: &String) -> usize {
s.len()
}
&s1 语法允许我们创建一个引用,该引用“引用”了 s1 的值,但不拥有它。因为引用不拥有它,所以当引用停止使用时,它指向的值不会被丢弃。
The &s1 syntax lets us create a reference that refers to the value of s1
but does not own it. Because the reference does not own it, the value it points
to will not be dropped when the reference stops being used.
同样,函数的签名使用 & 来指示参数 s 的类型是一个引用。让我们添加一些解释性注释:
Likewise, the signature of the function uses & to indicate that the type of
the parameter s is a reference. Let’s add some explanatory annotations:
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{s1}' is {len}.");
}
fn calculate_length(s: &String) -> usize { // s is a reference to a String
s.len()
} // Here, s goes out of scope. But because s does not have ownership of what
// it refers to, the String is not dropped.
变量 s 有效的作用域与任何函数参数的作用域相同,但当 s 停止使用时,引用指向的值不会被丢弃,因为 s 不具有所有权。当函数以引用作为参数而不是实际值时,我们不需要为了归还所有权而返回这些值,因为我们从未拥有过所有权。
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 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.
我们将创建引用的行为称为“借用”(borrowing)。就像在现实生活中一样,如果一个人拥有某样东西,你可以从他们那里借用。当你用完后,你必须还回去。你不拥有它。
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.
那么,如果我们尝试修改借来的东西会发生什么呢?尝试示例 4-6 中的代码。剧透警告:它行不通!
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!
fn main() {
let s = String::from("hello");
change(&s);
}
fn change(some_string: &String) {
some_string.push_str(", world");
}
这是错误信息:
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
|
8 | some_string.push_str(", world");
| ^^^^^^^^^^^ `some_string` is a `&` reference, so the data it refers to cannot be borrowed as mutable
|
help: consider changing this to be a mutable reference
|
7 | fn change(some_string: &mut String) {
| +++
For more information about this error, try `rustc --explain E0596`.
error: could not compile `ownership` (bin "ownership") due to 1 previous error
正如变量默认是不可变的一样,引用也是如此。我们不被允许修改我们拥有其引用的东西。
Just as variables are immutable by default, so are references. We’re not allowed to modify something we have a reference to.
可变引用
Mutable References
我们可以修复示例 4-6 中的代码,通过一些小改动来允许我们修改借用的值,即改用“可变引用”(mutable reference):
We can fix the code from Listing 4-6 to allow us to modify a borrowed value with just a few small tweaks that use, instead, a mutable reference:
fn main() {
let mut s = String::from("hello");
change(&mut s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
首先,我们将 s 更改为 mut。然后,在调用 change 函数的地方使用 &mut s 创建一个可变引用,并更新函数签名以接受一个可变引用 some_string: &mut String。这非常清楚地表明 change 函数将修改它借用的值。
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.
可变引用有一个很大的限制:如果你对一个值有一个可变引用,你不能再对该值有任何其他引用。尝试对 s 创建两个可变引用的代码将会失败:
Mutable references have one big restriction: If you have a mutable reference to
a value, you can have no other references to that value. This code that
attempts to create two mutable references to s will fail:
fn main() {
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
println!("{r1}, {r2}");
}
这是错误信息:
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
|
4 | let r1 = &mut s;
| ------ first mutable borrow occurs here
5 | let r2 = &mut s;
| ^^^^^^ second mutable borrow occurs here
6 |
7 | println!("{r1}, {r2}");
| -- first borrow later used here
For more information about this error, try `rustc --explain E0499`.
error: could not compile `ownership` (bin "ownership") due to 1 previous error
此错误说明此代码无效,因为我们不能在同一时间多次将 s 借用为可变的。第一个可变借用在 r1 中,并且必须持续到它在 println! 中使用为止,但在创建该可变引用与其使用之间,我们尝试在 r2 中创建另一个借用与 r1 相同数据的可变引用。
This error says that this code is invalid because we cannot borrow s as
mutable more than once at a time. The first mutable borrow is in r1 and must
last until it’s used in the println!, but between the creation of that
mutable reference and its usage, we tried to create another mutable reference
in r2 that borrows the same data as r1.
限制在同一时间内对同一数据进行多个可变引用,是为了以一种非常受控的方式允许修改。这是新 Rustacean 感到吃力的地方,因为大多数语言允许你随时随地进行修改。拥有此限制的好处是 Rust 可以在编译时防止数据竞争。“数据竞争”(data race)类似于竞态条件,当发生以下三种行为时会发生:
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:
-
两个或多个指针同时访问相同的数据。
-
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.
-
没有使用任何机制来同步对数据的访问。
-
There’s no mechanism being used to synchronize access to the data.
数据竞争会导致未定义行为,并且当你尝试在运行时追踪它们时,可能难以诊断和修复;Rust 通过拒绝编译具有数据竞争的代码来防止此问题!
Data races cause undefined behavior and can be difficult to diagnose and fix when you’re trying to track them down at runtime; Rust prevents this problem by refusing to compile code with data races!
一如既往,我们可以使用花括号来创建一个新作用域,从而允许存在多个可变引用,只是不能是“同时”存在的:
As always, we can use curly brackets to create a new scope, allowing for multiple mutable references, just not simultaneous ones:
fn main() {
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 r2 = &mut s;
}
Rust 对于结合可变引用和不可变引用也强制执行类似的规则。这段代码会导致错误:
Rust enforces a similar rule for combining mutable and immutable references. This code results in an error:
fn main() {
let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
let r3 = &mut s; // BIG PROBLEM
println!("{r1}, {r2}, and {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
|
4 | let r1 = &s; // no problem
| -- immutable borrow occurs here
5 | let r2 = &s; // no problem
6 | let r3 = &mut s; // BIG PROBLEM
| ^^^^^^ mutable borrow occurs here
7 |
8 | println!("{r1}, {r2}, and {r3}");
| -- immutable borrow later used here
For more information about this error, try `rustc --explain E0502`.
error: could not compile `ownership` (bin "ownership") due to 1 previous error
哇!当我们对同一个值有一个不可变引用时,我们“也”不能有一个可变引用。
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.
请注意,引用的作用域从引入它的地方开始,一直持续到最后一次使用该引用。例如,这段代码可以编译,因为不可变引用的最后一次使用是在 println! 中,在引入可变引用之前:
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
compile because the last usage of the immutable references is in the println!,
before the mutable reference is introduced:
fn main() {
let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
println!("{r1} and {r2}");
// Variables r1 and r2 will not be used after this point.
let r3 = &mut s; // no problem
println!("{r3}");
}
不可变引用 r1 和 r2 的作用域在它们最后一次使用的 println! 之后结束,这发生在创建可变引用 r3 之前。这些作用域不重叠,所以此代码是被允许的:编译器可以判断出在作用域结束之前的某个点,引用已不再被使用。
The scopes of the immutable references r1 and r2 end after the println!
where they are last used, which is before the mutable reference r3 is
created. These scopes don’t overlap, so this code is allowed: The compiler can
tell that the reference is no longer being used at a point before the end of
the scope.
尽管借用错误有时可能令人沮丧,但请记住,这是 Rust 编译器在尽早(在编译时而不是在运行时)指出潜在的错误,并向你显示问题的确切位置。这样,你就不必追踪为什么你的数据不是你想象中的那样了。
Even though borrowing errors may be frustrating at times, remember that it’s the Rust compiler pointing out a potential bug early (at compile time rather than at runtime) and showing you exactly where the problem is. Then, you don’t have to track down why your data isn’t what you thought it was.
悬垂引用
Dangling References
在具有指针的语言中,很容易由于释放了一些内存而保留了指向该内存的指针,从而错误地创建了“悬垂指针”(dangling pointer)——即引用了一个可能已被分配给其他人的内存位置的指针。相比之下,在 Rust 中,编译器保证引用永远不会是悬垂引用:如果你有一个对某些数据的引用,编译器将确保数据在引用离开作用域之前不会离开作用域。
In languages with pointers, it’s easy to erroneously create a dangling pointer—a pointer that references a location in memory that may have been given to someone else—by freeing some memory while preserving a pointer to that memory. In Rust, by contrast, the compiler guarantees that references will 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.
让我们尝试创建一个悬垂引用,看看 Rust 如何通过编译时错误来防止它们:
Let’s try to create a dangling reference to see how Rust prevents them with a compile-time error:
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String {
let s = String::from("hello");
&s
}
这是错误信息:
Here’s the error:
$ cargo run
Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0106]: missing lifetime specifier
--> src/main.rs:5:16
|
5 | fn dangle() -> &String {
| ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static`
|
5 | fn dangle() -> &'static String {
| +++++++
help: instead, you are more likely to want to return an owned value
|
5 - fn dangle() -> &String {
5 + fn dangle() -> String {
|
For more information about this error, try `rustc --explain E0106`.
error: could not compile `ownership` (bin "ownership") due to 1 previous error
此错误消息提到了一个我们尚未介绍的功能:生命周期。我们将在第 10 章详细讨论生命周期。但是,如果你忽略关于生命周期的部分,该消息确实包含了此代码为何存在问题的关键:
This error message refers to a feature we haven’t covered yet: lifetimes. We’ll discuss lifetimes in detail in Chapter 10. But, if you disregard 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
让我们仔细看看 dangle 代码的每个阶段究竟发生了什么:
Let’s take a closer look at exactly what’s happening at each stage of our
dangle code:
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String { // dangle returns a reference to a String
let s = String::from("hello"); // s is a new String
&s // we return a reference to the String, s
} // Here, s goes out of scope and is dropped, so its memory goes away.
// Danger!
因为 s 是在 dangle 内部创建的,当 dangle 的代码运行结束时,s 将被释放。但我们尝试返回一个指向它的引用。这意味着此引用将指向一个无效的 String。这可不行!Rust 不会让我们这样做。
Because s is created inside dangle, when the code of dangle is finished,
s will be deallocated. But we tried to return a reference to it. That means
this reference would be pointing to an invalid String. That’s no good! Rust
won’t let us do this.
这里的解决方案是直接返回 String:
The solution here is to return the String directly:
fn main() {
let string = no_dangle();
}
fn no_dangle() -> String {
let s = String::from("hello");
s
}
这没有任何问题。所有权被移出,没有任何东西被释放。
This works without any problems. Ownership is moved out, and nothing is deallocated.
引用的规则
The Rules of References
让我们回顾一下我们讨论过的关于引用的内容:
Let’s recap what we’ve discussed about references:
-
在任何给定的时间,你要么只能有一个可变引用,要么可以有任意数量的不可变引用。
-
At any given time, you can have either one mutable reference or any number of immutable references.
-
引用必须始终有效。
-
References must always be valid.
接下来,我们将看看另一种引用:切片(slices)。
Next, we’ll look at a different kind of reference: slices.