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的高明之處就在於幫助你在變異的時候檢查出潛在的問題,防止運行時出錯。


持續更新……

相關文章
相關標籤/搜索