# 引用和借用
> [references-and-borrowing.md](https://github.com/rust-lang/rust/blob/master/src/doc/book/references-and-borrowing.md)
commit 6ba952020fbc91bad64be1ea0650bfba52e6aab4
這篇教程是現行 3 個 Rust 所有權系統之一。所有權系統是 Rust 最獨特且最引人入勝的特性之一,也是作為 Rust 開發者應該熟悉的。Rust 所追求最大的目標 -- 內存安全,關鍵在于所有權。所有權系統有一些不同的概念,每個概念獨自成章:
- [所有權](#),關鍵章節
- 借用,你正在閱讀的這個章節
- [生命周期](#),關于借用的高級概念
這 3 章依次互相關聯,你需要完整地閱讀全部 3 章來對 Rust 的所有權系統進行全面的了解。
### 原則(Meta)
在我們開始詳細講解之前,這有兩點關于所有權系統重要的注意事項。
Rust 注重安全和速度。它通過很多*零開銷抽象*(*zero-cost abstractions*)來實現這些目標,也就是說在 Rust 中,實現抽象的開銷盡可能的小。所有權系統是一個典型的零開銷抽象的例子。本文提到所有的分析都是**在編譯時完成的**。你不需要在運行時為這些功能付出任何開銷。
然而,這個系統確實有一個開銷:學習曲線。很多 Rust 初學者會經歷我們所謂的“與借用檢查器作斗爭”的過程,也就是指 Rust 編譯器拒絕編譯一個作者認為合理的程序。這種“斗爭”會因為程序員關于所有權系統如何工作的基本模型與 Rust 實現的實際規則不匹配而經常發生。當你剛開始嘗試 Rust 的時候,你很可能會有相似的經歷。然而有一個好消息:更有經驗的 Rust 開發者反映,一旦他們適應所有權系統一段時間之后,與借用檢查器的沖突會越來越少。
記住這些之后,讓我們來學習關于借用的內容。
### 借用
在[所有權](#)章節的最后,我們有一個看起來像這樣的糟糕的函數:
~~~
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);
~~~
有如下錯誤:
~~~
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`,如果它不是,我們不能獲取一個不可變值的可變引用。
你也會發現我們在`y`前面加了一個星號(`*`),成了`*y`,這是因為`y`是一個`&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);
}
~~~
在上面的例子中,`y`在`x`之前被聲明,意味著`y`比`x`生命周期更長,這是不允許的。
- 前言
- 貢獻者
- 1.介紹
- 2.準備
- 3.學習 Rust
- 3.1.猜猜看
- 3.2.哲學家就餐問題
- 3.3.其它語言中的 Rust
- 4.語法和語義
- 4.1.變量綁定
- 4.2.函數
- 4.3.原生類型
- 4.4.注釋
- 4.5.If語句
- 4.6.循環
- 4.7.所有權
- 4.8.引用和借用
- 4.9.生命周期
- 4.10.可變性
- 4.11.結構體
- 4.12.枚舉
- 4.13.匹配
- 4.14.模式
- 4.15.方法語法
- 4.16.Vectors
- 4.17.字符串
- 4.18.泛型
- 4.19.Traits
- 4.20.Drop
- 4.21.if let
- 4.22.trait 對象
- 4.23.閉包
- 4.24.通用函數調用語法
- 4.25.crate 和模塊
- 4.26.const和static
- 4.27.屬性
- 4.28.type別名
- 4.29.類型轉換
- 4.30.關聯類型
- 4.31.不定長類型
- 4.32.運算符和重載
- 4.33.Deref強制多態
- 4.34.宏
- 4.35.裸指針
- 4.36.不安全代碼
- 5.高效 Rust
- 5.1.棧和堆
- 5.2.測試
- 5.3.條件編譯
- 5.4.文檔
- 5.5.迭代器
- 5.6.并發
- 5.7.錯誤處理
- 5.8.選擇你的保證
- 5.9.外部函數接口
- 5.10.Borrow 和 AsRef
- 5.11.發布途徑
- 5.12.不使用標準庫
- 6.Rust 開發版
- 6.1.編譯器插件
- 6.2.內聯匯編
- 6.4.固有功能
- 6.5.語言項
- 6.6.鏈接進階
- 6.7.基準測試
- 6.8.裝箱語法和模式
- 6.9.切片模式
- 6.10.關聯常量
- 6.11.自定義內存分配器
- 7.詞匯表
- 8.語法索引
- 9.參考文獻
- 附錄:名詞中英文對照