# 選擇你的保證
> [choosing-your-guarantees.md](https://github.com/rust-lang/rust/blob/master/src/doc/book/choosing-your-guarantees.md)
commit 6ba952020fbc91bad64be1ea0650bfba52e6aab4
Rust 的一個重要特性是允許我們控制一個程序的開銷和(安全)保證。
Rust 標準庫中有多種“wrapper 類型”的抽象,他們代表了大量在開銷,工程學和安全保證之間的權衡。很多讓你在運行時和編譯時增強之間選擇。這一部分將會詳細解釋一些特定的抽象。
在開始之前,強烈建議你閱讀Rust的[所有權](#)和[借用](#)。
### 基礎指針類型
### `Box<T>`
[Box\](http://doc.rust-lang.org/stable/std/boxed/struct.Box.html)是一個“自我擁有的”,或者“裝箱”的指針。因為它可以維持引用和包含的數據,它是數據的唯一的擁有者。特別的,當執行類似如下代碼時:
~~~
let x = Box::new(1);
let y = x;
// x no longer accessible here
~~~
這里,裝箱被*移動*進了`y`。因為`x`不再擁有它,此后編譯器不再允許程序猿使用`x`。相似的一個函數可以通過返回裝箱來*移出*函數。
當一個裝箱(還沒有被移動的)離開了作用域,析構函數將會運行。這個析構函數負責釋放內部的數據。
這是一個動態分配的零開銷抽象。如果你想要在堆上分配一些內存并安全的傳遞這些內存的指針,這是理想的情況。注意你將只能通過正常的借用規則來共享引用,這些在編譯時被檢查。
### `&T`和`&mut T`
這分別是不可變和可變引用。他們遵循“讀寫鎖”的模式,也就是你只可能擁有一個數據的可變引用,或者任意數量的不可變引用,但不是兩者都有。這個保證在編譯時執行,并且沒有明顯的運行時開銷。在大部分情況這兩個指針類型有能力在代碼塊之間廉價的共享引用。
這些指針不能在超出他們的生命周期的情況下被拷貝。
### `*const T`和`*mut T`
這些是C風格的指針,并沒附加生命周期或所有權。他們只是指向一些內存位置,沒有其他的限制。他們能提供的唯一的保證是除非在標記為`unsafe`的代碼中他們不會被解引用。
他們在構建像`Vec<T>`這樣的安全,低開銷抽象時是有用的,不過應該避免在安全代碼中使用。
### `Rc<T>`
這是第一個我們將會介紹到的有運行時開銷的包裝類型。
[Rc\](http://doc.rust-lang.org/stable/std/rc/struct.Rc.html)是一個引用計數指針。換句話說,這讓我們擁有相同數據的多個“有所有權”的指針,并且數據在所有指針離開作用域后將被釋放(析構函數將會執行)。
在內部,它包含一個共享的“引用計數”(也叫做“refcount”),每次`Rc`被拷貝時遞增,而每次`Rc`離開作用域時遞減。`Rc<T>`的主要職責是確保共享的數據的析構函數被調用。
這里內部的數據是不可變的,并且如果創建了一個循環引用,數據將會泄露。如果我們想要數據在存在循環引用時不被泄漏,我們需要一個垃圾回收器。
#### 保證
這里(`Rc<T>`)提供的主要保證是,直到所有引用離開作用域后,相關數據才會被銷毀。
當我們想要動態分配并在程序的不同部分共享一些(只讀)數據,且不確定哪部分程序會最后使用這個指針時,我們應該用`Rc<T>`。當`&T`不可能靜態地檢查正確性,或者程序員不想浪費時間編寫反人類的代碼時,它可以作為`&T`的可行的替代。
這個指針并*不是*線程安全的,并且Rust也不會允許它被傳遞或共享給別的線程。這允許你在不必要的情況下的原子性開銷。
`Rc<T>`有個姐妹版智能指針類型——`Weak<T>`。它是一個既沒有所有權、也不能被借用的智能指針。它也比較像`&T`,但并沒有生命周期的限制--一個`Weak<T>`可以一直存活。然而,嘗試對其內部數據進行訪問可能失敗并返回`None`,因為它可以比有所有權的`Rc`存活更久。這對循環數據結構和一些其他類型是有用的。
#### 開銷
隨著內存使用增加,`Rc<T>`是一次性的分配,雖然相比一個常規`Box<T>`它會多分配額外兩個字(也就是說,兩個`usize`值)。(“強”引用計數相比“弱”引用計數)。
`Rc<T>`分別在拷貝和離開作用域時會產生遞增/遞減引用計數的計算型開銷。注意拷貝將不會進行一次深度復制,相反它會簡單的遞增內部引用計數并返回一個`Rc<T>`的拷貝。
### Cell 類型
`Cell`提供內部可變性。換句話說,他們包含的數據可以被修改,即便是這個類型并不能以可變形式獲取(例如,當他們位于一個`&`指針或`Rc<T>`之后時)。
[對此`cell`模塊的文檔有一個非常好的解釋](http://doc.rust-lang.org/stable/std/cell/)。
這些類型*經常*在結構體字段中出現,不過他們也可能在其他一些地方找到。
### `Cell<T>`
[Cell\](http://doc.rust-lang.org/stable/std/cell/struct.Cell.html)是一個提供了零開銷內部可變性的類型,不過只用于`Copy`類型。因為編譯器知道它包含的值對應的所有數據都位于棧上,所以并沒有通過簡單的替換數據而導致任何位于引用之后的數據泄露(或者更糟!)的擔心。
然而使用這個封裝仍有可能違反你自己的不可變性,所以謹慎的使用它。它是一個很好的標識,表明一些數據塊是可變的并且可能在你第一次讀取它和當你想要使用它時的值并不一樣。
~~~
use std::cell::Cell;
let x = Cell::new(1);
let y = &x;
let z = &x;
x.set(2);
y.set(3);
z.set(4);
println!("{}", x.get());
~~~
注意這里我們可以通過多個不可變的引用改變相同的值。
這與如下代碼有相同的運行時開銷:
~~~
let mut x = 1;
let y = &mut x;
let z = &mut x;
x = 2;
*y = 3;
*z = 4;
println!("{}", x);
~~~
不過它有額外的優勢,它確實能夠編譯成功。(高級黑?)
#### 保證
這個類型放寬了當沒有必要時“沒有因可變性導致的混淆”的限制。然而,這也放寬了這個限制提供的保證;所以當你的不可變量依賴存儲在`Cell`中的數據,你應該多加小心。
這對改變基本類型和其他`Copy`類型非常有用,當通過`&`和`&mut`的靜態規則并沒有其他簡單合適的方法改變他們的值時。
`Cell`并不讓你獲取數據的內部引用,它讓我們可以自由改變值。
#### 開銷
使用`Cell<T>`并沒有運行時開銷,不過你使用它來封裝一個很大的(`Copy`)結構體,可能更適合封裝單獨的字段為`Cell<T>`因為每次寫入都會是一個結構體的完整拷貝。
### `RefCell<T>`
[RefCell\](http://doc.rust-lang.org/stable/std/cell/struct.RefCell.html)也提供了內部可變性,不過并不限制為`Copy`類型。
相對的,它有運行時開銷。`RefCell<T>`在運行時使用了讀寫鎖模式,不像`&T`/`&mut T`那樣在編譯時執行。這通過`borrow()`和`borrow_mut()`函數來實現,它修改一個內部引用計數并分別返回可以不可變的和可變的解引用的智能指針。當智能指針離開作用域引用計數將被恢復。通過這個系統,我們可以動態的確保當有一個有效的可變借用時絕不會有任何其他有效的借用。如果程序猿嘗試創建一個這樣的借用,線程將會恐慌。
~~~
use std::cell::RefCell;
let x = RefCell::new(vec![1,2,3,4]);
{
println!("{:?}", *x.borrow())
}
{
let mut my_ref = x.borrow_mut();
my_ref.push(1);
}
~~~
與`Cell`相似,它主要用于難以或不可能滿足借用檢查的情況。大體上我們知道這樣的改變不會發生在一個嵌套的形式中,不過檢查一下是有好處的。
對于大型的,復雜的程序,把一些東西放入`RefCell`來將事情變簡單是有用的。例如,Rust編譯器內部的[`ctxt`結構體](http://doc.rust-lang.org/stable/rustc/middle/ty/struct.ctxt.html)中的很多map都在這個封裝中。他們只會在創建時被修改一次(但并不是正好在初始化后),或者在明顯分開的地方多次多次修改。然而,因為這個結構體被廣泛的用于各個地方,有效的組織可變和不可變的指針將會是困難的(也許是不可能的),并且可能產生大量的難以擴展的`&`指針。換句話說,`RefCell`提供了一個廉價(并不是零開銷)的方式來訪問它。之后,如果有人增加一些代碼來嘗試修改一個已經被借用的cell時,這將會產生(通常是決定性的)一個恐慌,并會被追溯到那個可惡的借用上。
相似的,在Servo的DOM中有很多可變量,大部分對于一個DOM類型都是本地的,不過有一些交錯在DOM中并修改了很多內容。使用`RefCell`和`Cell`來保護所有的變化可以讓我們免于擔心到處都是的可變性,并且同時也表明了何處*正在*發生變化。
注意如果是一個能用`&`指針的非常簡單的情形應該避免使用`RefCell`。
#### 保證
`RefCell`放寬了避免混淆的改變的*靜態*限制,并代之以一個*動態*限制。保證本身并沒有改變。
#### 開銷
`RefCell`并不分配空間,不過它連同數據還包含一個額外的“借用狀態”指示器(一個字的大小)。
在運行時每次借用產生一次引用計數的修改/檢查。
### 同步類型(Synchronous types)
上面的很多類型不能以一種線程安全的方式使用。特別是`Rc<T>`和`RefCell<T>`,他們都使用非原子的引用計數(*原子*引用計數可以在不引起數據競爭的情況下在多個線程中遞增),不能在多線程中使用。這讓他們使用起來更廉價,不過我們也需要這兩個類型的線程安全版本。他們以`Arc<T>`和`Mutex<T>`/`RWLock<T>`的形式存在。
注意非線程安全的類型*不能*在線程間傳遞,并且這是在編譯時檢查的。
### `Arc<T>`
[Arc\](http://doc.rust-lang.org/stable/std/sync/struct.Arc.html)就是一個使用原子引用計數版本的`Rc<T>`(*Atomic reference count*,因此是“Arc”)。它可以在線程間自由的傳遞。
C++的`shared_ptr`與`Arc`類似,然而C++的情況中它的內部數據總是可以改變的。為了語義上與C++的形式相似,我們應該使用`Arc<Mutex<T>>`,`Arc<RwLock<T>>`,或者`Arc<UnsafeCell<T>>`[1](#)。最后一個應該只被用在我們能確定使用它并不會造成內存不安全性的情況下。記住寫入一個結構體不是一個原子操作,并且很多像`vec.push()`這樣的函數可以在內部重新分配內存并產生不安全的行為,所以即便是單一環境也不足以證明`UnsafeCell`是安全的。
#### 保證
類似`Rc`,它提供了當最后的`Arc`離開作用域時(不包含任何的循環引用)其內部數據的析構函數將被執行的(線程安全的)保證。
#### 開銷
使用原子引用計數有額外的開銷(無論是被拷貝或者離開作用域時都會發生)。當在一個單獨的線程中通過一個`Arc`共享數據時,任何時候都更傾向于使用`&`指針。
### `Mutex<T>`和`RwLock<T>`
[Mutex\](http://doc.rust-lang.org/stable/std/sync/struct.Mutex.html)和[RwLock\](http://doc.rust-lang.org/stable/std/sync/struct.RwLock.html)通過RAII guard(guard是一類直到析構函數被調用時能保持一些狀態的對象)提供了互斥功能。對于這兩個類型,mutex直到我們調用`lock()`之前它都是無效的,此時直到我們獲取鎖這個線程都會被阻塞,同時它會返回一個guard。這個guard可以被用來訪問它的內部數據(可變的),而當guard離開作用域鎖將被釋放。
~~~
{
let guard = mutex.lock();
// guard dereferences mutably to the inner type
*guard += 1;
} // lock released when destructor runs
~~~
`RwLock`對多線程讀有額外的效率優勢。只要沒有writer,對于共享的數據總是可以安全的擁有多個reader;同時`RwLock`讓reader們獲取一個“讀取鎖”。這樣的鎖可以并發的獲取并通過引用計數記錄。writer必須獲取一個“寫入鎖”,它只有在所有reader都離開作用域時才能獲取。
#### 保證
這兩個類型都提供了線程間安全的共享可變性,然而他們易于產生死鎖。一些額外的協議層次的安全性可以通過類型系統獲取。
#### 開銷
他們在內部使用類原子類型來維持鎖,這樣的開銷非常大(他們可以阻塞處理器所有的內存讀取知道他們執行完畢)。而當有很多并發訪問時等待這些鎖也將是很慢的。
### 組合(Composition)
閱讀Rust代碼時的一個常見的痛苦之處是遇到形如`Rc<RefCell<Vec<T>>>`這樣的類型(或者諸如此類的更復雜的組合)。這些組合式干什么的,和為什么作者會選這么一個類型(以及何時你應該在自己的代碼中使用這樣一個類型)的理由并不總是顯而易見的。
通常,將你需要的保證組合到一起是一個例子,而不為無關緊要的東西產生開銷。
例如,`Rc<RefCell<T>>`就是一個這樣的組合。`Rc<T>`自身并不能可變的解引用;因為`Rc<T>`可以共享,而共享的可變性可以導致不安全的行為,所以我們在其中放入`RefCell<T>`來獲得可以動態驗證的共享可變性。現在我們有了共享的可變數據,不過它只能以只有一個writer(沒有reader)或多個reader的方式共享。
現在,我們可以更進一步,并擁有`Rc<RefCell<Vec<T>>>`或`Rc<Vec<RefCell<T>>>`,他們都是可共享可改變的vector,不過他們并不一樣。
前者,`RefCell<T>`封裝了`Vec<T>`,所以`Vec<T>`整體是可變的。與此同時,同一時刻只能有一個整個`Vec`的可變借用。這意味著你的代碼不能同時通過不同的`Rc`句柄來操作vector的不同元素。然而,我們可以隨意的從`Vec<T>`中加入或取出元素。這類似于一個有運行時借用檢查的`&mut Vec<T>`。
后者,借用作用于單獨的元素,不過vector整體是不可變的。因此,我們可以獨立的借用不同的元素,不過我們對vector加入或取出元素。這類似于`&mut [T]`[2](#),不過同樣會在運行時做借用檢查。
在并發程序中,我們有一個使用`Arc<Mutex<T>>`的類似場景,它提供了共享可變性和所有權。
當閱讀使用這些類型的代碼時,一步步的閱讀并關注他們提供的保證/開銷。
當選擇一個組合類型的時候,我們必須反過來思考;搞清楚我們需要何種保證,以及在組合中的何處我們需要他們。例如,如果面對一個`Vec<RefCell<T>>`和`RefCell<Vec<T>>`之間的選擇,我們需要明確像上面講到的那樣的權衡并選擇其一。
1
> .
`Arc<UnsafeCell<T>>`
> 實際上并不能編譯因為
`UnsafeCell<T>`
> 并不是
`Send`
> 或
`Sync`
> 的,不過我們可以把它 wrap 進一個類型并且手動為其實現
`Send`
> /
`Sync`
> 來獲得
`Arc<Wrapper<T>>`
> ,它的
`Wrapper`
> 是
`struct Wrapper<T>(UnsafeCell<T>)`
> 。
[ ?](# "Jump back to footnote [1] in the text.")
2
> .
`&[T]`
> 和
`&mut [T]`
> 是
*切片*
> (slice);他們包含一個指針和一個長度并可以引用一個vector或數組的一部分。
`&mut [T]`
> 能夠改變它的元素,不過長度不能改變。
[ ?](# "Jump back to footnote [2] in the text.")
- 前言
- 貢獻者
- 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.參考文獻
- 附錄:名詞中英文對照