`
zcmit
  • 浏览: 17955 次
文章分类
社区版块
存档分类
最新评论

Rust中文翻译28

 
阅读更多
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);

我们得到这个错误:


分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics