Rust 基础笔记之浅谈 References and Borrowing

Borrowing

先来看一段代码:指针

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的概念。code

什么是References(引用):就像C语言的指针,在Rust中相似于&v这样的语法来引用v,这样的话意味着是去引用v的资源而不是拥有它(就是借来用用和永久占有它的区别),我不知道我这样说有没有问题,但大概意思应该是这样的。ip

这里就该引出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 references(可变引用)

这是引用的第二种形式&mut T,而以前的则是不可变引用&T,可变引用容许你修改你借来的资源,就像这样:it

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。io

那为何中间的两句代码要用{}括起来的,在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没有超出其做用域),东西都不在你这,你怎么用?

接下来接得讲讲borrow的规则啦

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的高明之处就在于帮助你在变异的时候检查出潜在的问题,防止运行时出错。


持续更新……

相关文章
相关标签/搜索