引用与借用
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();
(s, length)
}
以上元组代码有这样一个问题:我们必须将String
返回给调用函数,以便在调用calculate_length
后仍能使用String
,因为String
被移动到了calcualte_length
内。相反我们可以提供一个String
值的引用。引用想一个指针,因为它是一个地址,我们可以由此访问存储于该地址的属于其他变量的数据。与指针不同,引用确保指向某个特定类型的有效值。
下面定义一个新的calculate_length
函数,它以一个对象的引用作为参数,而不是获取值的所有权。
fn main() {
let s1 = String::from("hello");
let len = calculate_length_new(&s1);
println!("The length of '{}' is {}.", s2, len);
}
fn calculate_length_new(s: &String) -> usize {
s.len()
}
注意我们传递&s1
给calclate_length_new
,同时在函数定义中,我们获取&String
而不是String
。这个==&==符号就是引用,它们允许你使用值但不获取其所有权。
仔细看看这个函数调用
let s1 = String::from("hello");
let len = calulate_length(&s1);
&s1
语法让我们创建一个指向值s1
的引用,但是并不拥有它。因为并不拥有它,所以当引用停止使用时,它所指向的值也不会被丢弃。
同理,函数签名使用&
来表明参数s
的类型是一个引用。
我们将创建一个引用的行为称为借用。正如现实生活中,如果一个人拥有某样东西,你可以从他那里借来。当你使用完毕,必须要还回去。我们并不拥有它。
那么我们尝试修改借用的变量呢?行不通
fn main() {
let s = String::from("hello");
change(&s);
}
fn change(s: &String) {
s.push_str(", world!");
}
上述代码报错
error[E0596]: cannot borrow `*s` as mutable, as it is behind a `&` reference
--> src/main.rs:15:5
|
15 | s.push_str(", world!");
| ^ `s` is a `&` reference, so the data it refers to cannot be borrowed as mutable
正如变量默认是不可变的,引用也是一样。默认不允许修改引用的值
可变引用
我们通过一个小调整就能修复上述代码中的错误,允许我们修改一个借用的值,这就是可变引用。
fn main() {
let mut s = String::from("hello");
change_new(&mut s);
}
fn change_new(s: &mut String) {
s.push_str(", world!")
}
首先,我们必须将s
改为mut
.然后调用change
函数的地方创建一个可变引用&mut s
,并更新函数签名以接受一个可变引用s: &String
。这就非常清楚的表明,change
函数将改变它所借用的值。
可变引用有一个很大的限制:如果你有一个对该变量的可变引用,就不能再创建对该变量的引用。这些尝试创建两个s
的可变引用的代码会失败:
fn main() {
let mut s = String::from("hello");
let ref1 = &mut s;
let ref2 = &mut s;
println!("{} , {}", ref1, ref2);
}
报错如下:
error[E0499]: cannot borrow `s` as mutable more than once at a time
--> src/main.rs:18:16
|
17 | let ref1 = &mut s;
| ------ first mutable borrow occurs here
18 | let ref2 = &mut s;
| ^^^^^^ second mutable borrow occurs here
19 | println!("{} , {}", ref1, ref2);
| ---- first borrow later used here
这个报错说这段代码是无效的,因为我们不能在同一时间多次将s
作为可变变量借用。第一个可变的介入在ref1
中,并且必须持续到pringln!
中使用它,但是在那个可变引用的创建和它的使用之间,我们又尝试在ref2
中创建另一个可变引用,该引用借用与r1
相同的数据。
这一限制以一种非常小心谨慎的方式允许可变性,防止同一时间对同一数据存在多个可变引用。这个限制的好处是Rust可以在编译时就避免数据竞争。数据竞争类似于竞态条件,它可由这三个行为造成:
- 两个或更多指针同时访问同一数据
- 至少有一个指针被用来写入数据
- 没有同步数据访问机制
数据竞争会导致未定义行为,难以在运行时追踪,并且难以诊断和修复;Rust避免了这种情况的发生,因为它甚至不会编译存在数据竞争的代码!
一如既往,可以使用大括号来创建一个新的作用域,以允许拥有多个可变引用,只是不能同时拥有:
fn main() {
let mut s = String::from("hello");
{
let r1 = &mut s;
} // r1 在这里离开了作用域,所以我们完全可以创建一个新的引用
let r2 = &mut s;
}
Rust在同时使用可变与不可变引用是也采用类似的规则。这些代码会导致一个错误:
fn main() {
let mut s = String::from("hello");
let r1 = &s; // 没问题
let r2 = &s; // 没问题
let r3 = &mut s; // 大问题
println!("{}, {}, and {}", r1, r2, r3);
}
错误如下:
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
--> src/main.rs:33:14
|
31 | let r1 = &s; // 没问题
| -- immutable borrow occurs here
32 | let r2 = &s; // 没问题
33 | let r3 = &mut s; // 大问题
| ^^^^^^ mutable borrow occurs here
34 | println!("{}, {}, and {}", r1, r2, r3);
| -- immutable borrow later used here
哇哦!我们也不能在拥有不可变引用的同时拥有可变引用。
不可变引用的用户可不希望在他们的眼皮底下值就被意外的改变了!然而,多个不可变引用是可以的,因为没有拿个只会读取数据的人有能力影响其他人读取到的数据。
注意一个引用的作用域从声明的地方开始一直持续到最后一次使用为止。例如,因为最后一次使用不可变引用(pringln!
),发生在声明可变引用之前,所以如下代码是可以编译的:
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{} and {}", r1, r2);
let r2 = &mut s;
println!("{}", r2);
}
不可引用r1
和r2
的作用域在println!
最后一次使用之后结束,这也是创建可变引用r3
的地方。他们的作用域没有重叠,所以代码是可以编译的。编译器可以在作用域结束之前判断不再使用的引用。
垂悬引用
在具有指针的语言中,很容易通过释放内存事保留它的指针而错误的生成一个垂悬指针,所谓垂悬指针是其指向的内存可能已经被分配给其他持有者。相比之下,在Rust中编译器确保引用永远也不会变成垂悬状态:当你拥有一些数据的引用,编译器确保数据不会在其引用之前离开作用域。
尝试创建一个垂悬引用:
fn main() {
let referece_to_nothing = dangle();
}
fn dangle() -> &String {
let s = String::from("hello");
&s
}
这里是错误:
error[E0106]: missing lifetime specifier
--> src/main.rs:35:16
|
35 | 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
|
35 | fn dangle() -> &'static String {
| +++++++
因为s
是在dangle
函数内创建的,当dangle
的代码执行完毕后,s
将被释放。不过我们尝试返回它的引用。这意味着这个引用会指向一个无效的String
,Rust不允许我们这么做。
这里的解决方法是直接返回String
:
fn no_dangle() -> String {
let s = String::from("hello");
s
}
这样就没有任何错误了。所有权被移动出去,所以值没有被释放。
引用的规则
让我们概述一下之前对引用的讨论:
- 在任意给定时间,要么只能有一个可变引用,要么只能有多个不可变引用。
- 引用必须是总是有效的。