标签:编程语言 rust 安全
5.9 引用和借用
本节是Rust三处描述所有权系统的其中之一.所有权是Rust最独特和引人注目的特性,这也是Rust程序员必须熟悉的一个特性.所有权使Rust得以实现它最大的设计目标,内存安全.这里有一些不同的概念,每一个都有自己的章节:
- 所有权,你正在读的
- 借用(borrowing, 5.9), 以及它的关联特性‘引用‘
- 生命期(5.10),以及borrowing的高级特性
这三者是相关的,也是循序渐进的.你必须要完全理解这个三个部分.
5.9.1 元
在我们讨论细节之前,有两个关于所有权系统的重要说明.
Rust致力于安全和快速.它实现这个目标通过"零花费的抽象(zero-cost abstractions)",也就是说在Rust中,抽象花费最少的代价.我们在本章中讨论的所有分析都是在编译时完成的.你不需要为运行时开销费心.
然而,这个系统仍然有一些开销:学习曲线.很多Rust的初学者都会经历我们成为"与借用检测做斗争"的过程.也就是说Rust编译器拒绝编译程序员认为是可用的代码.因为程序员关于所有权如果运行的想法和真正的Rust规则经常发生冲突.你在开始的时候也会尽力类似的情况.然而这是好消息,更多有经验的Rust程序员反馈说一旦他们适应了这个规则一段时间以后,他们就会斗争的越来越少.
有了这个铺垫,我们来学习引用和借用.
5.9.2 借用
在所有权结束的时候,我们有一个令人不快的函数:
fn foo(v1: Vec<i32>, v2: Vec<i32>) -> (Vec<i32>, Vec<i32>, i32) {
// do stuff with v1 and v2
// hand back ownership, and the result of our function
(v1, v2, 42)
}
let v1 = vec![1, 2, 3];
let v2 = vec![1, 2, 3];
let (v1, v2, answer) = foo(v1, v2);
这在Rust中并不是惯用的,并没有利用借用.这是第一步:
fn foo(v1: &Vec<i32>, v2: &Vec<i32>) -> i32 {
// do stuff with v1 and v2
// return the answer
42
}
let v1 = vec![1, 2, 3];
let v2 = vec![1, 2, 3];
let answer = foo(&v1, &v2);
// we can use v1 and v2 here!
我们使用&Vec<i32>作为参数,而不是Vec<i32>.传递了&v1和&v2,而不是v1和v2.我们称&T类型为一个引用,相比拥有多有权,它只是借用了所有权.一个对借用的绑定不会在离开作用域的时候释放内存.这就是说foo()结束的时候,我们可以继续试用原来的绑定.
引用是不可变的,和绑定一样.这就是说,在foo()内部,向量不能被改变:
fn foo(v: &Vec<i32>) {
v.push(5);
}
let v = vec![];
foo(&v);
会出现错误:
向向量中添加值,会修改向量,我们不被允许这样做.
5.9.3 &mut 引用
第二种类型的引用是: &mut T.一个可变引用允许你修改你引用的对象的值.例如:
let mut x = 5;
{
let y = &mut x;
*y += 1;
}
println!("{}", x);
这会打印6.我们让y成为一个x的可变引用,然后增加y的值.你会注意x被标注为了mut,如果不是,我们无法创建一个不可变值的可变借用.
另一方面,&mut引用和引用一样.尽管他们俩有很大的不同.你可以说上面的例子太造作,因为我们需要一个额外的作用域,我们使用了{}.如果我们拿走括号,会有错误:
正如它描述的,这里有些规则.
5.9.4 规则
Rust关于借用的规则:
首先,任何借用都必须存在于一个比所有者稍微小的作用域当中.第二,你可以试用下述一种借用形式,但是不能两个同时使用:
- 一个资源的0到多个引用(&T)
- 只用一个可变引用(&mut T)
你会发现这个和数据竞争的场景很类似,但是不完全一样:
当两个或者更多指针同时访问同一块内存时就会发生数据竞争,其中至少有一个是写,并且操作不是同步的.
使用引用时,你可以用你想用的任意多数,其中没有一个是写的.如果你写的时候,你需要两个或者更多指针指向同一块内存.而你只能同时拥有一个&mut引用.这就是Rust阻止数据竞争的方式,在编译时:如果违反了规则就会有编译错误.
想着这个规则,我们再来看看我们的例子.
考虑作用域
代码如下:
let mut x = 5;
let y = &mut x;
*y += 1;
println!("{}", x);
这段代码又如下错误:
这是因为我们违反了规则:我们有了一个&mut T指向x,所以我们不能再创建任何&T了.两者选其一.note指出了改正的方法:
也就是说,可变借用存在于剩下的所有代码中.我们想要的是可变借用在我们调用println!之前就能结束,然后我们使用一个不可变借用.在Ruat中,借用和作用域密切相关,作用域是借用生效的范围.我们的作用域是这样的:
let mut x = 5;
let y = &mut x; // -+ &mut borrow of x starts here
// |
*y += 1; // |
// |
println!("{}", x); // -+ - try to borrow x here
// -+ &mut borrow of x ends here
作用域冲突:我们不能创建一个&x,当y还在其作用域中的时候.
当我们增加了括号:
let mut x = 5;
{
let y = &mut x; // -+ &mut borrow starts here
*y += 1; // |
} // -+ ... and ends here
println!("{}", x); // <- try to borrow x here
这就没问题了.我们的可变借用在调用不可变借用之前就离开了作用域.作用域就是我们寻找借用存在时间长短的关键.
借用防止出现哪些问题:
为什么要有如此严格的规则?我们之前说过,这些规则防止了数据竞争.数据竞争会引起哪些问题?比如:
迭代器失效:一个例子就是迭代器失效,发生在当你试图修改一个集合而你正在遍历它的时候.Rust的借用检测器会阻止此事发生:
let mut v = vec![1, 2, 3];
for i in &v {
println!("{}", i);
}
这会打印出1到3.当我们遍历向量的时候,我们只有元素的引用.v是向量的一个不可变借用,也就是说我们不能在遍历它的时候修改它:
let mut v = vec![1, 2, 3];
for i in &v {
println!("{}", i);
v.push(34);
}
有错误:
我们不能修改v,因为它被循环借用了.
释放后使用: 引用必须和它所指向的资源生存的一样长.Rust会检查引用的作用域来保证这一点.
如果Rust不检查这一点,我们会不小心使用一个已经失效的引用.例如:
let y: &i32;
{
let x = 5;
y = &x;
}
println!("{}", y);
我们得到这个错误:
换句话说,y只能存在于x的作用域之中.当x离开作用域,y立即失效.所以,错误说借用‘生活的不够长‘,它在那个时刻已经失效了.
同样的问题引用声明在它指向的对象之前:
let y: &i32;
let x = 5;
y = &x;
println!("{}",y);
我们得到这个错误:
Rust中文翻译28
标签:编程语言 rust 安全
原文地址:http://blog.csdn.net/zcmit/article/details/46946709