【Rust学习】04_2引用和借用
前言
在这章我们将开始学习Rust的引用和借用,它们是Rust中重要的概念,它们允许我们创建可变引用,以及创建不可变引用。
内容
引用和借用
在下面的示例中,我们必须将 String 返回给调用函数,以便在调用 calculate_length 后仍能使用 String,因为 String 被移动到了 calculate_length 内。
下面是如何定义并使用一个(新的)calculate_length 函数,它以一个对象的引用作为参数而不是获取值的所有权:
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1, len);
}
fn calculate_length(s: &String) -> usize {
s.len()
}
首先,请注意变量声明和函数返回值中的所有元组代码都消失了。注意我们传递 &s1 给 calculate_length,同时在函数定义中,我们获取 &String 而不是 String。
这些 & 符号表示引用,它们允许您引用某个值,而无需获得其所有权。
注意:与使用 & 引用相反的操作是 解引用(dereferencing),它使用解引用运算符,*。
让我们仔细看看这里的函数调用:
let s1 = String::from("hello");
let len = calculate_length(&s1);
该 &s1 语法允许我们创建一个指向 s1 的引用 ,但不拥有它。因为它不拥有它,所以当引用停止使用时,它指向的值不会被删除。
同样,函数的签名用 & 来表明参数 s 的类型是引用。让我们添加一些解释性注释:
fn calculate_length(s: &String) -> usize { // s 是对 String 的引用
s.len()
} // 这里,s 离开了作用域。但因为它并不拥有引用值的所有权,
// 所以什么也不会发生
变量 s 有效的作用域与任何函数参数的作用域相同,但是当停止使用 s 时,引用指向的值不会被删除,因为它没有 s 的所有权。当函数将引用作为参数而不是实际值时,我们不需要返回值来归还所有权,因为我们从未拥有所有权。
我们将创建一个引用的行为称为 借用(borrowing)。正如现实生活中,如果一个人拥有某样东西,你可以从他那里借来。当你使用完毕,必须还回去。
如果我们尝试修改借来的变量,会发生什么呢?
fn main() {
let s = String::from("hello");
change(&s);
}
fn change(some_string: &String) {
some_string.push_str(", world");
}
现在我们来运行一下,这时候我们会得到一个错误:
error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference
--> src/main.rs:19:5
|
19 | 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
|
18 | fn change(some_string: &mut String) {
| +++
正如变量默认情况下是不可变的一样,引用也是不可变的。不允许修改我们引用的内容。
可变引用
们只需进行一些小的调整来修改借来的值,这些调整使用可变引用:
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 函数将改变它所借用的值。
不过可变引用有一个很大的限制:在同一时间,只能有一个对某一特定数据的可变引用。尝试创建两个可变引用的代码将会失败:
let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
let r3 = &mut s; // BIG PROBLEM
println!("{}, {}, and {}", r1, r2, r3);
错误如下:
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
--> src/main.rs:37:14
|
35 | let r1 = &s; // no problem
| -- immutable borrow occurs here
36 | let r2 = &s; // no problem
37 | let r3 = &mut s; // BIG PROBLEM
| ^^^^^^ mutable borrow occurs here
38 |
39 | println!("{}, {}, and {}", r1, r2, r3);
| -- immutable borrow later used here
我们不能有一个可变的引用,因为我们有一个不可变的引用指向相同的值。
不可变引用的用户不希望值突然从他们下面改变出来!但是,允许多个不可变引用,因为任何只读取数据的人都无法影响其他任何人对数据的读取。
请注意,引用的范围从引入它的地方开始,一直持续到最后一次使用该引用。例如,此代码将进行编译,因为不可变引用的最后一次使用(println!)发生在引入可变引用之前:
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 之前。这些作用域不重叠,因此允许使用此代码:编译器可以看出在作用域结束之前的某个时间点不再使用引用。
尽管借用错误有时可能会令人沮丧,但请记住,这是 Rust 编译器尽早指出潜在的错误(在编译时而不是在运行时),并准确地告诉你问题出在哪里。这样,你就不必追踪为什么你的数据不是你想象的那样。
悬垂引用
在具有指针的语言中,很容易通过释放内存时保留指向它的指针而错误地生成一个 悬垂指针(dangling pointer),所谓悬垂指针是其指向的内存可能已经被分配给其它持有者。相比之下,在 Rust 中编译器确保引用永远也不会变成悬垂状态:当你拥有一些数据的引用,编译器确保数据不会在其引用之前离开作用域。
让我们尝试创建一个悬垂引用,Rust 会通过一个编译时错误来避免:
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String {
let s = String::from("hello");
&s
}
错误如下:
error[E0106]: missing lifetime specifier
--> src/main.rs:60:16
|
60 | 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`
|
60 | fn dangle() -> &'static String {
| +++++++
help: instead, you are more likely to want to return an owned value
|
60 - fn dangle() -> &String {
60 + fn dangle() -> String {
|
此错误消息指的是我们尚未介绍的功能:生存期。我们将在第 10 章中详细讨论生命周期。但是,如果您忽略有关生存期的部分,则该消息确实包含了为什么此代码有问题的关键:
this function's return type contains a borrowed value, but there is no value
for it to be borrowed from
让我们仔细看看在dangle(悬垂)
代码的每个阶段到底发生了什么:
fn dangle() -> &String { // dangle 返回一个字符串的引用
let s = String::from("hello"); // s 是一个新的字符串
&s // 返回字符串s的引用
} // 这时候 s 的作用域结束,s 被丢弃,s 的内存被释放。
// Danger!
因为 s 是在 dangle 函数内部创建的,所以当 dangle 的代码完成后,s 将被释放。当我们试图返回对它的引用。这意味着此引用将指向无效的 String。这可不对!Rust 不允许我们这样做。
这里的解决方案是直接返回 String:
fn no_dangle() -> String {
let s = String::from("hello");
s
}
这样就没有任何错误了。所有权被移动出去,所以没有值被释放。
引用的规则
让我们回顾一下我们讨论过的关于引用的内容:
- 在任意给定时间,要么 只能有一个可变引用,要么 只能有多个不可变引用。
- 引用必须总是有效的。
接下来,我们将查看一种不同类型的引用:切片(slices)。