陪老 K 学 Rust (四)
借用和引用
1 词法空间
不久以前,Rust 的变量作用域是基于词法的,最近一年(可能)Rust 合并了 非词法作用域
生命周期的特性 (NLL, No Lexical Liftime),使得变量的生命周期不再严格遵循词法域了,关于 NLL 的详细情况可以参考这篇文章:https://zhuanlan.zhihu.com/p/32884290 .
下面的代码演示了基于词法域的变量生命周期:
#[derive(Debug)]
struct Foobar(i32);
impl Drop for Foobar {
fn drop(&mut self) {
println!("Dropping a Foobar: {:?}", self);
}
}
Fn main() {
println!("Before x");
let _x = Foobar(1);
println!("After x");
{
println!("Before y");
let _y = Foobar(2);
println!("After y");
}
println!("End of main");
}
在
x
和y
变量之前加下划线是为了抑制 Rust 编译器的报错,对于不使用的变量, Rust 会发出编译警告。
运行代码可以看出变量 _x
, _y
的生命周期是严格遵循作用域的。
Before x
After x
Before y
After y
Dropping a Foobar: Foobar(2)
End of main
Dropping a Foobar: Foobar(1)
如果去掉多余的 {}
, 猜测一下变量 _x
和 _y
的生命周期?它们会不是以创建的逆序释放呢?验证一下:
#[derive(Debug)]
struct Foobar(i32);
impl Drop for Foobar {
fn drop(&mut self) {
println!("Dropping a Foobar: {:?}", self);
}
}
fn main() {
println!("Before x");
let _x = Foobar(1);
println!("After x");
println!("Before y");
let _y = Foobar(2);
println!("After y");
println!("End of main");
}
Before x
After x
Before y
After y
End of main
Dropping a Foobar: Foobar(2)
Dropping a Foobar: Foobar(1)
可以看出,释放是按照创建的 逆序 进行的,值得信赖!
2 借用和引用
很多情况下,我们希望在不转移值的所有权(不改变变量的属主)的情况下使用变量。很简单,Rust 提供了一种叫做 引用
的机制来满足我们的需求。 借用
和 引用
是一回事,只是概念的侧重点不一致。 借用
是针对 所有权机制
而言的。 引用
是形式,是针对变量使用的方式而言的。
通常变量变量的使用方式遵循两种形式:
值拷贝
和引用
.值拷贝
是通过复制一个新的值进行使用,在参数传递(通常的值传参
),赋值等操作中使用.引用
是通过值复制值所在的地址进行使用的,典型的应用就是在引用传参
, 值共享等场景。
编译下面的代码:
#[derive(Debug)]
struct Foobar(i32);
impl Drop for Foobar {
fn drop(&mut self) {
println!("Dropping a Foobar: {:?}", self);
}
}
fn uses_foobar(foobar: Foobar) {
println!("I consumed a Foobar: {:?}", foobar);
}
fn main() {
let x = Foobar(1);
uses_foobar(x);
uses_foobar(x);
}
编译器会输出如下的错误:
error[E0382]: use of moved value: `x`
--> l15.rs:19:17
|
16 | let x = Foobar(1);
| - move occurs because `x` has type `Foobar`, which does not implement the `Copy` trait
17 |
18 | uses_foobar(x);
| - value moved here
19 | uses_foobar(x);
| ^ value used here after move
error: aborting due to previous error
For more information about this error, try `rustc --explain E0382`.
第二个uses_foobar(x);
使用了所有权已经转移的值。根据编译器的建议,我们可以使用几种方法来修复:
- 对于
Foobar
类型,我们实现Copy
trait. - 对于
uses_foobar
函数,我们使用引用传参
的方式借用
Foobar(1)
的所有权,如同在Drop
trait 里面的drop
函数的第一个参数self
那样。
3 同时引用
不象所有权属主,一个值可以同时被多次以 引用
的方式使用。如下代码段:
#[derive(Debug)]
struct Foobar(i32);
impl Drop for Foobar {
fn drop(&mut self) {
println!("Dropping a Foobar: {:?}", self);
}
}
fn uses_foobar(foobar: &Foobar) {
println!("I consumed a Foobar: {:?}", foobar);
}
fn main() {
let x: Foobar = Foobar(1);
let y: &Foobar = &x;
println!("Before uses_foobar");
uses_foobar(&x);
uses_foobar(y);
println!("After uses_foobar");
}
在这里, Foobar(1)
两次被以引用的方式使用,一次是作为 引用参数
直接传递给
uses_foobar
, 另外一次是被变量 y
以应用的方式使用,并以参数的方式传递给
uses_foobar
. 在这段代码中,局部变量 y
的类型是显示声明的,而不是使用的 =类型
= 推断的方式。代码输出如下:
Before uses_foobar
I consumed a Foobar: Foobar(1)
I consumed a Foobar: Foobar(1)
After uses_foobar
Dropping a Foobar: Foobar(1)
代码可以正常运行的原因在于。
- 多次的 只读 引用不会引发数据竟态。
- 值本身的生命周期要比引用的生命周期长,也就是说,变量
x
要比变量y
的生命周期长。
std::mem::drop
函数可以主动触发值的失效操作。使用此函数来结束变量 x
的值的生命周期。
#[derive(Debug)]
struct Foobar(i32);
impl Drop for Foobar {
fn drop(&mut self) {
println!("Dropping a Foobar: {:?}", self);
}
}
fn uses_foobar(foobar: &Foobar) {
println!("I consumed a Foobar: {:?}", foobar);
}
fn main() {
let x: Foobar = Foobar(1);
let y: &Foobar = &x;
println!("Before uses_foobar");
uses_foobar(&x);
std::mem::drop(x);
uses_foobar(y);
println!("After uses_foobar");
}
编译器检查出借用的生命周期超出了其所有权属主的生命周期。
error[E0505]: cannot move out of `x` because it is borrowed
--> l17.rs:19:20
|
16 | let y: &Foobar = &x;
| -- borrow of `x` occurs here
...
19 | std::mem::drop(x);
| ^ move out of `x` occurs here
20 | uses_foobar(y);
| - borrow later used here
error: aborting due to previous error
For more information about this error, try `rustc --explain E0505`
4 可变引用
当然,我们也可以以 可变引用
的方式来使用某个值,为了避免数据出现竟态,Rust 不允许同时出现多个 可变引用
或者在被可变引用的情况下以其他方式(包括 只读引用
)访问。
fn main() {
let x: Foobar = Foobar(1);
let y: &mut Foobar = &mut x;
println!("Before uses_foobar");
uses_foobar(&x); // 编译报错
std::mem::drop(x);
uses_foobar(y);
println!("After uses_foobar");
}
error[E0596]: cannot borrow `x` as mutable, as it is not declared as mutable
--> l17.rs:16:26
|
15 | let x: Foobar = Foobar(1);
| - help: consider changing this to be mutable: `mut x`
16 | let y: &mut Foobar = &mut x;
| ^^^^^^ cannot borrow as mutable
error[E0502]: cannot borrow `x` as immutable because it is also borrowed as mutable
--> l17.rs:18:17
|
16 | let y: &mut Foobar = &mut x;
| ------ mutable borrow occurs here
17 | println!("Before uses_foobar");
18 | uses_foobar(&x); // 编译报错
| ^^ immutable borrow occurs here
19 | std::mem::drop(x);
20 | uses_foobar(y);
| - mutable borrow later used here
error[E0505]: cannot move out of `x` because it is borrowed
--> l17.rs:19:20
|
16 | let y: &mut Foobar = &mut x;
| ------ borrow of `x` occurs here
...
19 | std::mem::drop(x);
| ^ move out of `x` occurs here
20 | uses_foobar(y);
| - borrow later used here
error: aborting due to 3 previous errors
Some errors have detailed explanations: E0502, E0505, E0596.
For more information about an error, try `rustc --explain E0502`