借用
Rust语言体用了 Borrowing 的概念,可以让Rust变量其他编程语言那样随意改变变量的值。
借用(Borrowing):顾名思义,即一个变量借用另一个变量的资源所有权。
不可变的借用
符号 &
表示对资源的借用。
let y = &x // y借用x的资源访问权。y引用了资源。
println!("{:?}",y) // x的所有权没有转移。但已经可以通过y访问x的资源。
y被称为资源的引用。 当引用变量离开作用域,即释放对资源的引用。
Rust 默认 &
是一种不可变的借用。即借用所有权的变量不可以修改资源的值。
fn main() {
let x: Vec<i32> = vec!(1, 2, 3);
let y = &x; // y借用x对资源的所有权。vec的所有权仍然是x的。
println!("x={:?}, y={:?}", x, y);
}
&x即对x的Borrowing。这不会发生所有权实际的转移。所以println没有编译异常。
不可变引用类型
借用到资源引用的变量,是一种特殊的类型。不再是资源原始的类型了。
let y = &x // y借用x的资源访问权。y只是引用了资源。
若x
的类型为T
,则&x
的类型为&T
,y
的类型为&T
。这是一个引用类型。
需要有引用类型这个概念,这可以帮我们更好的理解Rust。
对于代码:
let a = 100;
let y = &a;
a的类型为i32,y的类型为&i32。y不可变,即y不能再与其他引用绑定。
对于代码:
let b = 200;
let mut y = &b;
b的类型为i32,y的类型为&i32。y可变,即y可以与其他引用绑定。
例:y可以与不同引用类型绑定。
fn main() {
let a = 100;
let b = 200;
let mut y = &a;
y = &b;
println!("{:?},{:?}", a,y);
}
对于
let mut y = &a;
该语句会引发编译告警,但编译可通过。编译器认为这句语句没必要。直接写成let mut y = &b;
即可。可以看到Rust编译器非常智能。此处只是为了演示,真实场景不太可能写出这样的语句。
可变的借用
Rust也提供了一种可变资源的借用。
使用 &mut
符号表示对可变资源的引用。
若需要访问可变资源的引用,必须在变量前使用*
来访问资源。
例:使用可变引用。
fn main() {
let mut x: i32 = 100;
// 可变借用
let y: &mut i32 = &mut x;
// y的类型是&mut i32,无法使用'+=',*y指向实际资源。使用'+='修改资源值。
*y += 100;
println!("{}", *y); // 打印输出 200 。
// 或者,可以写成。 println!("{}", y);
}
&mut i32
可以看作是一种可变i32的引用类型。
所以let y: &mut i32 = &mut x
可简写为:let y = &mut x
这段代码最后并没有直接访问x,而是打印输出的y。
为什么这段代码不直接写println!("{:?}", x);
这是有原因的。
若上面的例子写成:
fn main() {
let mut x: i32 = 100;
let y: &mut i32 = &mut x;
*y += 100;
println!("{}", x);
}
编译报错。 为什么? 要解决这个问题,需要理解如下规则。
Rust的借用规则:
- 同一个作用域中,一个资源只有一个可变借用(&mut T),但拥有可变借用(&mut T)后就不能有不可变借用(&T)。
- 同一个作用域中,一个资源可以有多个不可变借用(&T),但拥有不可变借用(&T)后就不能有可变借用(&mut T)。
- 借用在离开作用域后释放。
- 可变借用(&mut T)释放前不可访问原变量。
- 若变量已经被借用,则变量的所有权不可再被move。
对于规则1、2:
简单来说,可变借用与不可变借用在同一作用域是互斥的,且可变结合两两互斥。
借用规则非常像“读写锁”。
即同一时刻,同一资源,只能拥有一个“写锁”,或只能拥有多个“读锁”,不允许“写锁”和“读锁”在同一时刻同时出现。
- 可变引用(&mut T)相当于写锁。
- 不可变引用(&T)相当于读锁。
这是数据读写过程中保障一致性的典型做法。Rust在编译中完成借用的检查,保证了程序在运行时不会出错。
对于规则3,4:
简单来说就是:归还借用,借走不碰。
借来的东西,总是要还的。一定要注意应该在何处何时正确的“归回”引用。
东西一旦借给别人使用,自己触碰不到了。所以也无法访问。
对于规则5:
别人正在用的东西,我自己也不该去动。
所以,对于上面代码若需要修改正确,则需要y释放掉可变引用。简单的做法就是,让y提前在一个作用域中释放掉。
fn main() {
let mut x: i32 = 100;
{
let y: &mut i32 = &mut x;
*y += 100; // 修改 y 指定的资源。
} // 释放可变引用。
println!("{}", x); // 该作用域汇总,x不再有其他可变引用。所以可以访问。
}
总结借用的可变性
Borrowing 分为 不可变引用(&T)与 可变引用(&mut T)。
“不可变引用”是只读的,不可更新被引用的内容。且不可变变量在同一个作用域可以有多个不可变借用。
例:
fn main() {
let x: Vec<i32> = vec!(1i32, 2, 3);
//可同时有多个不可变借用
let y = &x;
let z = &x;
let m = &x;
//ok
println!("{:?}, {:?}, {:?}, {:?}", x, y, z, m);
}
使用“可变引用”的条件比较苛刻。同一个作用域中,可变变量只能有一个可变引用(&mut T),且被借用的变量本身必须有可变性。若可变借用必须释放所有权,否则原变量无法访问。
fn main() {
//源变量x可变性
let mut x: Vec<i32> = vec!(1i32, 2, 3);
let y = &mut x; // 一个可变借用。
// let z = &mut x; // 只能有一个可变借用错误,所以该语句会编译错误。
y.push(100);
//ok
println!("{:?}", y);
// println!("{:?}", x); // 错误,可变借用y未释放,原变量x不可访问。
} // 函数结束,y释放销毁。
关于可变借用有一些技巧:
- 使用 { } 语句块可以在函数内回收被借走的所有权。
- 原始变量被可变借用后不可再访问,但可变借用的变量依然可以继续被借用。
例:
fn main() {
let mut x: Vec<i32> = vec!(1i32, 2, 3);
//更新数组
//push中对数组进行了可变借用,并在push函数退出时销毁这个借用
x.push(10);
{
//可变借用1
let mut y = &mut x;
y.push(100);
//可变借用2,注意:此处是对y的借用,不可再对x进行借用,
//因为y在此时依然存活。
let z = &mut y;
z.push(1000);
println!("{:?}", z); //打印: [1, 2, 3, 10, 100, 1000]
} //y和z在此处被销毁,并释放借用。
//访问x正常
println!("{:?}", x); //打印: [1, 2, 3, 10, 100, 1000]
}
总结
- 借用不改变内存的所有者(Owner),借用只是对源内存的临时引用。
- 在借用周期内,借用方可以读写这块内存,所有者被禁止读写内存;且所有者保证在有“借用”存在的情况下,不会释放或转移内存。
- 失去所有权的变量不可以被借用(访问)。
- 在租借期内,内存所有者保证不会释放/转移/可变租借这块内存,但如果是在非可变租借的情况下,所有者是允许继续非可变租借出去的。
- 借用周期满后,所有者收回读写权限.
- 借用周期小于被借用者(所有者)的生命周期。
辨析:Rust为什么需要借用?
如果Rust没有借用而只拥有所有权的概念,会有什么问题?
存在一个函数:该函数的参数类型没有实现Copy特性,现在需要对传入的参数进行一些计算。
例:计算动态数组的长度。
fn main() {
let v = vec![1,2,3];
let v_len = length(v);
println!("{:?}", v_len);
}
fn length(vec: Vec<i32>) -> usize {
vec.len()
}
这段代码没有任何问题。输出 3 。
但是,在如果在length函数调用后仍需要使用变量v,则编译出错。
fn main() {
let v = vec![1,2,3];
let v_len = length(v);
println!("{:?}", v_len);
println!("{:?}", v); // 添加的这一行编译报错。
}
fn length(vec: Vec<i32>) -> usize {
vec.len()
}
由于最初v的所有权被传入length函数的参数vec中。对着length函数调用完毕,所有权没有从函数中传回来,所以导致v对自定义的所有权丢失。
若没有借用的概念,如何解决所有权丢失的问题呢?即通过返回值来返回所有权。
fn main() {
let v = vec![1,2,3];
let (v_len,v) = length(v); // 变量v重新获取资源的所有权。
println!("{:?}", v_len);
println!("{:?}", v);
}
fn length(vec: Vec<i32>) -> (usize,Vec<i32>) {
(vec.len(),vec) // 将资源的所有权转移回去。
}
这种方法实在太累赘。使用Rust的借用,可以更好的解决所有权丢失的问题。
fn main() {
let v = vec![1,2,3];
let v_len = length(&v);
println!("{:?}", v_len); // 打印 3
println!("{:?}", v); // 打印 [1,2,3]
}
fn length(vec: &Vec<i32>) -> usize {
vec.len()
}