先来看一段代码:
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的习惯,也许你仅仅了解完Ownership的概念的时候,也只能写出这样的代码,似乎没有其他什么好的办法了,现在就让我们来看一看References 和 Borrowing的概念。
什么是References(引用):就像C语言的指针,在Rust中类似于&v这样的语法来引用v,这样的话意味着是去引用v的资源而不是拥有它(就是借来用用和永久占有它的区别),我不知道我这样说有没有问题,但大概意思应该是这样的。
这里就该引出borrow的概念了,let v2 = &v;的意思是v2引用了v的的资源,v2借用(borrow)了v对资源的所有权,既然是借,用完了就得还,这和日常生活中你借别人的东西的过程是一样的,当v2超出了它的作用域的时候,系统并不会释放该资源,因为是借的嘛(这里和move的概念就有区别了,move就差不多是这东西我送给你了),用完之后就得将所有权归还给v,接着我们又可以重新使用v了。
在上面的例子中的v是immutable(不可修改的),那么借用了v的所有权的v2也是immutable(不可修改的),强行修改v2的值的话,就会发生一下错误:
error: cannot borrow immutable borrowed content `*v` as mutable
我们把上面的代码改写一下:
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>是什么类型,我们把形如&T的都叫做"reference(引用)",这意味着,这将不是拥有(owning)资源,而是借来用用(borrow)。当变量v1和v2超出其作用域的时候,系统将会将对应的资源交回给源变量,也就是说当我们在调用foo()之后,我们又可以重新使用原来的绑定了。
这是引用的第二种形式&mut T,而之前的则是不可变引用&T,可变引用允许你修改你借来的资源,就像这样:
let mut x = 5; { let y =&mut x; *y += 1; } println!("{}", x);
这是官方手册里的例子,需要强调的是什么呢?有人好奇说,y不是不可变的吗?怎么能修改它的值呢?这里需要说明一下,y的确是不可变的,这里的不可变指的是y本身是不可变的,也就是说y只能指向x而不能修改成其他的引用,就像这样y = &mut z这是不允许的,因为y引用的是x的资源,x是可变的,*y += 1;就相当于 x += 1;这里是可变的,y自始自终指向的都是x。
那为什么中间的两句代码要用{}括起来的,在C语言中{}是用来表示语句块的,从而限制变量的作用域,在Rust中也是这样,一旦离开{},那么y就超出了它的作用域,又因为y是引用的x,相当于y从x那里借来(borrow)的资源,现在y使用完了,那么所有权就得重新回到了x上,所以最后一句输出代码才能正确调用,如果去掉{},则会给出以下错误:
error: cannot borrow `x` as immutable because it is also borrowed as mutable println!("{}", x); ^ note: previous borrow of `x` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `x` until the borrow ends let y = &mut x; ^ note: previous borrow ends here fn main() { } ^
意思就是说呢,你x的资源不是借给y了吗?人家还没用完(y没有超出其作用域),东西都不在你这,你怎么用?
1.任何borrow的作用域都得比owner小才行,就拿上面的例子来说,y的作用域小于x,如果不是会出现什么样的错误呢?代码是这样的:
let y; let mut x = 5; y = &mut x; *y += 1; //这里就不输出啦y还没有归还所有权呢!
错误是这样的:
main.rs:4:14: 4:15 error: `x` does not live long enough …………(后面的就省略啦)
说的很清楚 x活的没y长 。
2.这一点很重要,&T这种类型的引用可以有0或N个,但是&mut T这种类型的引用只能存在一个,如果不是,你看看下面的代码:
let mut x = 5; let y = &mut x; let z = &mut x;
main.rs:4:18: 4:19 error: cannot borrow `x` as mutable more than once at a time
想想也是,还是生活中你借东西的例子,你怎么能把一个东西借给两个或多个人“用”呢?如果只是借给两个或多个人看的话,那没问题,你借给多少人看(相当于&T)都行,但你要借给两个或多个人一起“用(&mut T)”的话?那还不得打起来啊,这就是文档中说的"data race"数据竞争,这里是官方的定义,很容易懂的:
There is a ‘data race’ when two or more pointers access the same memory location at the same time, where at least one of them is writing, and the operations are not synchronized.
下面的代码就没什么问题啦,都是拿来看的:
let mut x = 5; let y = &x; let z = &x;
作用域对于borrow来说是一个很重要的东西,需要好好理解一下。
举个例子来说,你借给别人东西,你至少得告诉他什么时候还吧,作用域就是干这个的,只有在规定的作用域中,变量才能正常的使用资源,一旦其超出了作用域,也就是说不在对该资源有使用权了,资源自然要还给原来的绑定,这是官方文档中的原话:
scope is the key to seeing how long a borrow lasts for.
意思是说作用域决定了borrow可以持续多久,文档中还举了两个例子,来解释borrow的概念中容易犯错误的地方:
1.Iterator invalidation(迭代中的问题)
let mut v=vec![1, 2, 3]; for i in &v { println!("{}", i); }
这段代码是可以正常执行的,但是编译器会给一个警告,如下:
main.rs:2:9: 2:14 warning: variable does not need to be mutable, #[warn(unused_mut)] on by default main.rs:2 let mut v = vec![1, 2, 3];
很贴心哈,它告诉我们说v是不需要被修改的,注意到这问题可能会消除很多潜在的问题。注意一下:这里的i引用了v的资源。
但下面的代码就有问题了:
let mut v=vec![1, 2, 3]; for i in &v { println!("{}", i); v.push(4); }
错在哪呢?很明显i borrow了v的资源,并且i还在其作用域中,所以这个时候v并没有对该资源的所有权,所以才会报这样的错误:
error: cannot borrow `v` as mutable because it is also borrowed as immutable
2.use after free
这是个老生长谈的问题了,使用被释放的资源肯定会出问题,所以在Rust中, 引用得活的和它所引用的资源一样久才行 ,Rust在编译的时候会帮你检查这个问题,来阻止运行时出错。
例如下面的代码:
let y: &i32; { let x = 5; y = &x; } println!("{}", y);
编译器给出了这样的错误:
error: `x` does not live long enough y = &x; …………
这里的x显然没有y活的久,下面的代码也是一样的道理:
let y: &i32; let x = 5; y = &x; println!("{}", y);
y的作用域是从其申明开始,也就是第一行,很明显x的作用域小于y,这段代码要是换成C语言来写的话,是可以正常执行的,当Rust的高明之处就在于帮助你在变异的时候检查出潜在的问题,防止运行时出错。
持续更新……