這篇教程是現行3個Rust所有權系統之一。所有權系統是Rust最獨特且最引人入勝的特性之一,也是作為Rust開發者應該熟悉的。Rust所追求最大的目標 -- 內存安全,關鍵在于所有權。所有權系統有一些不同的概念,每個概念獨自成章:
* [所有權](http://kaisery.gitbooks.io/rust-book-chinese/content/content/5.8.Ownership%20%E6%89%80%E6%9C%89%E6%9D%83.md),關鍵章節
* 借用,你正在閱讀的這個章節
* [生命周期](http://kaisery.gitbooks.io/rust-book-chinese/content/content/5.10.Lifetimes%20%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F.md),關于借用的高級概念
這3章依次互相關聯,你需要完整地閱讀全部3章來對Rust的所有權系統進行全面的了解。
## 原則(Meta)
在我們開始詳細講解之前,這有兩點關于所有權系統重要的注意事項。
Rust注重安全和速度。它通過很多_零開銷抽象_(_zero-cost abstractions_)來實現這些目標,也就是說在Rust中,實現抽象的開銷盡可能的小。所有權系統是一個典型的零開銷抽象的例子。本文提到所有的分析都是**在編譯時完成的**。你不需要在運行時為這些功能付出任何開銷。
然而,這個系統確實有一個開銷:學習曲線。很多Rust初學者會經歷我們所謂的“與借用檢查器作斗爭”的過程,也就是指Rust編譯器拒絕編譯一個作者認為合理的程序。這種“斗爭”會因為程序員關于所有權系統如何工作的基本模型與Rust實現的實際規則不匹配而經常發生。當你剛開始嘗試Rust的時候,你很可能會有相似的經歷。然而有一個好消息:更有經驗的Rust開發者反應,一旦他們適應所有權系統一段時間之后,與借用檢查器的沖突會越來越少。
記住這些之后,讓我們來學習關于借用的內容。
## 借用
在[所有權](http://doc.rust-lang.org/nightly/book/ownership.html)章節的最后,我們有一個看起來像這樣的糟糕的函數:
~~~
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`作為我們的參數,我們獲取一個引用:`&Vec`。并與其直接傳遞`v1`和`v2`,我們傳遞`&v1`和`&v2`。我們稱`&T`類型為一個”引用“,而與其擁有這個資源,它借用了所有權。一個借用變量的綁定在它離開作用域時并不釋放資源。這意味著`foo()`調用之后,我們可以再次使用原始的綁定。
引用是不可變的,就像綁定一樣。這意味著在`foo()`中,向量完全不能被改變:
~~~
fn foo(v: &Vec<i32>) {
v.push(5);
}
let v = vec![];
foo(&v);
~~~
有如下錯誤:
~~~
error: cannot borrow immutable borrowed content `*v` as mutable
v.push(5);
^
~~~
放入一個值改變了向量,所以我們不允許這樣做
## `&mut`引用
這有第二種類型的引用:`&mut T`。一個“可變引用”允許你改變你借用的資源。例如:
~~~
let mut x = 5;
{
let y = &mut x;
*y += 1;
}
println!("{}", x);
~~~
這會打印`6`。我們讓`y`是一個`x`的可變引用,接著把`y`指向的值加一。你會注意到`x`也必須被標記為`mut`,如果它不是,我們不能獲取一個不可變值的可變引用。
否則,`&mut`引用就像一個普通引用。這兩者之間_有_巨大的區別,以及它們如何交互的。你可以說在上面的例子中有些地方是可疑的,因為我們需要額外的作用域,包圍在`{`和`}`之間。如果我們移除它們,我們得到一個錯誤:
~~~
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() {
}
^
~~~
它們被證明是規則。
## 規則
Rust中的借用有一些規則:
第一,任何借用必須位于比擁有著更小的作用域。第二,你可以有一個或另一個這兩種類型的借用,不過不能同時擁有它們(這兩種):
* 0個或N個資源的引用(&T)
* 只有1個可變引用((&mut T)
你可能注意到這些非常的熟悉,雖然并不完全一樣,它類似于數據競爭的定義:
> 當2個或更多個指針同時訪問同一內存位置,當它們中至少有1個在寫,同時操作并不是同步的時候存在一個“數據競爭”
通過引用,你可以擁有你像擁有的任意多的引用,因為它們沒有一個在寫。如果你在寫,并且你需要2個或更多相同內存的指針,則你只能一次擁有一個`&mut`。這就是Rust如何在編譯時避免數據競爭:我們會得到錯誤,如果我們打破規則的話。
在記住這些之后,讓我們再次考慮我們的例子。
## 理解作用域(Thinking in scopes)
這是代碼:
~~~
let mut x = 5;
let y = &mut x;
*y += 1;
println!("{}", x);
~~~
這些代碼給我們如下錯誤:
~~~
error: cannot borrow `x` as immutable because it is also borrowed as mutable
println!("{}", x);
^
~~~
這是因為我們違反了規則:我們有一個指向`x`的`&mut T`,所以我們不允許創建任何`&T`。一個或另一個。錯誤記錄提示了我們應該如何理解這個錯誤:
~~~
note: previous borrow ends here
fn main() {
}
^
~~~
換句話說,可變借用在剩下的例子中一直存在。我們需要的是可變借用在我們嘗試調用`println!`_之前_結束并生成一個不可變借用。在Rust中,借用綁定在借用有效的作用域上。而我們的作用域看起來像這樣:
~~~
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
~~~
這些作用域沖突了:我們不能在`y`在作用域中時生成一個`&x`。
所以我們增加了一個大括號:
~~~
let mut x = 5;
{
let y = &mut x; // -+ &mut borrow starts here
*y += 1; // |
} // -+ ... and ends here
println!("{}", x); // <- try to borrow x here
~~~
這就沒有問題了。我們的可變借用在我們創建一個不可變引用之前離開了作用域。不過作用域是看清一個借用持續多久的關鍵。
## 借用避免的問題(Issues borrowing prevents)
為什么要有這些限制性規則?好吧,正如我們記錄的,這些規則避免了數據競爭。數據競爭能造成何種問題呢?這里有一些。
### 迭代器失效(Iterator invalidation)
一個例子是“迭代器失效”,它在當你嘗試改變你正在迭代的集合時發生。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);
}
~~~
這里是錯誤:
~~~
error: cannot borrow `v` as mutable because it is also borrowed as immutable
v.push(34);
^
note: previous borrow of `v` occurs here; the immutable borrow prevents
subsequent moves or mutable borrows of `v` until the borrow ends
for i in &v {
^
note: previous borrow ends here
for i in &v {
println!(“{}”, i);
v.push(34);
}
^
~~~
我們不能修改`v`因為它被循環借用。
### 釋放后使用
引用必須與它引用的值活的一樣長。Rust會檢查你的引用的作用域來保證這是正確的。
如果Rust并沒有檢查這個屬性,我們可能意外的使用了一個無效的引用。例如:
~~~
let y: &i32;
{
let x = 5;
y = &x;
}
println!("{}", y);
~~~
我們得到這個錯誤:
~~~
error: `x` does not live long enough
y = &x;
^
note: reference must be valid for the block suffix following statement 0 at
2:16...
let y: &i32;
{
let x = 5;
y = &x;
}
note: ...but borrowed value is only valid for the block suffix following
statement 0 at 4:18
let x = 5;
y = &x;
}
~~~
換句話說,`y`只在`x`存在的作用域中有效。一旦`x`消失,它變成無效的引用。為此,這個錯誤說借用“并沒有活的足夠久”因為它在應該有效的時候是無效的。
當引用在它引用的變量_之前_聲明會導致類似的問題:
~~~
let y: &i32;
let x = 5;
y = &x;
println!("{}", y);
~~~
我們得到這個錯誤:
~~~
error: `x` does not live long enough
y = &x;
^
note: reference must be valid for the block suffix following statement 0 at
2:16...
let y: &i32;
let x = 5;
y = &x;
println!("{}", y);
}
note: ...but borrowed value is only valid for the block suffix following
statement 1 at 3:14
let x = 5;
y = &x;
println!("{}", y);
}
~~~
- 前言
- 1.介紹
- 2.準備
- 2.1.安裝Rust
- 2.2.Hello, world!
- 2.3.Hello, Cargo!
- 3.學習Rust
- 3.1.猜猜看
- 3.2.哲學家就餐問題
- 3.3.其它語言中的Rust
- 4.高效Rust
- 4.1.棧和堆
- 4.2.測試
- 4.3.條件編譯
- 4.4.文檔
- 4.5.迭代器
- 4.6.并發
- 4.7.錯誤處理
- 4.8.外部語言接口
- 4.9.Borrow 和 AsRef
- 4.10.發布途徑
- 5.語法和語義
- 5.1.變量綁定
- 5.2.函數
- 5.3.原生類型
- 5.4.注釋
- 5.5.If語句
- 5.6.for循環
- 5.7.while循環
- 5.8.所有權
- 5.9.引用和借用
- 5.10.生命周期
- 5.11.可變性
- 5.12.結構體
- 5.13.枚舉
- 5.14.匹配
- 5.15.模式
- 5.16.方法語法
- 5.17.Vectors
- 5.18.字符串
- 5.19.泛型
- 5.20.Traits
- 5.21.Drop
- 5.22.if let
- 5.23.trait對象
- 5.24.閉包
- 5.25.通用函數調用語法
- 5.26.包裝箱和模塊
- 5.27.`const`和`static`
- 5.28.屬性
- 5.29.`type`別名
- 5.30.類型轉換
- 5.31.關聯類型
- 5.32.不定長類型
- 5.33.運算符和重載
- 5.34.`Deref`強制多態
- 5.35.宏
- 5.36.裸指針
- 6.Rust開發版
- 6.1.編譯器插件
- 6.2.內聯匯編
- 6.3.不使用標準庫
- 6.4.固有功能
- 6.5.語言項
- 6.6.鏈接參數
- 6.7.基準測試
- 6.8.裝箱語法和模式
- 6.9.切片模式
- 6.10.關聯常量
- 7.詞匯表
- 8.學院派研究
- 勘誤