22
33到目前為止,我們討論的所有程式碼都在編譯期強制加上 Rust 記憶體安全保證。然而,Rust 內部其實隱藏了第二種語言,並不強制加上這些記憶體安全保證:這語言叫做** 不安全(unsafe)的 Rust** ,可和常規 Rust 一樣正常執行,同時賦予我們極強的能力。
44
5- 不安全的 Rust 之所以存在,是由於靜態分析本質上過於保守。當編譯器嘗試確認程式碼是否遵守這些安全保證時,比起接受一些非法的程式,更寧願拒絕部分合法程式 。儘管有些程式碼** 看起來** 正確,但 Rust 無法獲取足夠資訊保證的話,它就是會擋下來。在這些案例中,你可以寫不安全程式碼並告訴編譯器:「相信我,我知道我在幹麻。」從反面來看這也有缺點,你必須自行承擔風險:若誤用不安全程式碼,可能會造成記憶體不安全,例如發生對空指標(null pointer)解參考。
5+ 不安全的 Rust 之所以存在,是由於靜態分析本質上過於保守。當編譯器嘗試確認程式碼是否遵守這些安全保證時,比起接受一些非法的程式,更寧願拒絕部分有效的程式 。儘管有些程式碼** 看起來** 正確,但 Rust 無法獲取足夠資訊保證的話,它就是會擋下來。在這些案例中,你可以寫不安全程式碼並告訴編譯器:「相信我,我知道我在幹麻。」從反面來看這也有缺點,你必須自行承擔風險:若誤用不安全程式碼,可能會造成記憶體不安全,例如發生對空指標(null pointer)解參考。
66
77Rust 擁有另一個不安全的自我的另一理由是電腦硬體本質上就不安全。如果 Rust 不允許這些不安全操作,就無法完成特定任務。Rust 必須允許你做這些底層系統程式設計,例如直接與作業系統互動,甚至撰寫自己的作業系統。系統程式設計是這個語言的目標之一,一起探索我們可以用不安全的 Rust 做什麼和如何使用吧。
88
@@ -18,7 +18,7 @@ Rust 擁有另一個不安全的自我的另一理由是電腦硬體本質上就
1818
1919需要謹記在心的是,` unsafe ` 並不會關閉借用檢查器(borrow checker)或是停用其他 Rust 的安全檢查:在不安全程式碼中操作一個參考仍然會經過檢查。` unsafe ` 關鍵字只提供上述不經由編譯器檢查記憶體安全的五項功能,在不安全區塊內你依然保有一定程度的安全性。
2020
21- 此外,` unsafe ` 並不意味在此區塊內的程式碼一定有風險或有記憶體安全問題:其目的是作為一個程式設計師,你必須確保在 ` unsafe ` 區塊內的程式碼透過合法途徑存取記憶體 。
21+ 此外,` unsafe ` 並不意味在此區塊內的程式碼一定有風險或有記憶體安全問題:其目的是作為一個程式設計師,你必須確保在 ` unsafe ` 區塊內的程式碼透過有效途徑存取記憶體 。
2222
2323錯誤因人類不可靠而發生。不過,將五種不安全操作標記在 ` unsafe ` 區塊內,讓你得知任何記憶體安全相關的錯誤一定在某個 ` unsafe ` 內。請將 ` unsafe ` 區塊保持夠小,當你在調查一個記憶體錯誤時,會慶幸當初有這麼做。
2424
@@ -28,12 +28,12 @@ Rust 擁有另一個不安全的自我的另一理由是電腦硬體本質上就
2828
2929### 對裸指標解參考
3030
31- 在第四章[ 「迷途參考」] [ 迷途參考 ] 一節,我們提及編譯器確保參考一定是合法的 。不安全的 Rust 有兩種新型別叫** 裸指標** ,和參考非常相似。和參考一樣,裸指標能是不可變或可變,分別寫做 ` *const T ` 和 ` *mut T ` 。星號不是解參考運算子,它就是型別名稱的一部分。在裸指標的脈絡下,** 不可變** 代表指標不能在被解參考之後直接賦值。
31+ 在第四章[ 「迷途參考」] [ 迷途參考 ] 一節,我們提及編譯器確保參考一定是有效的 。不安全的 Rust 有兩種新型別叫** 裸指標** ,和參考非常相似。和參考一樣,裸指標能是不可變或可變,分別寫做 ` *const T ` 和 ` *mut T ` 。星號不是解參考運算子,它就是型別名稱的一部分。在裸指標的脈絡下,** 不可變** 代表指標不能在被解參考之後直接賦值。
3232
3333和參考與智慧指標(smart pointer)不同,裸指標是:
3434
3535* 允許忽略借用規則,同時可存在指向相同位置的可變和不可變的指標,或是多個可變指標
36- * 不能保證一定指向合法記憶體
36+ * 不能保證一定指向有效記憶體
3737* 可以為空(null)
3838* 並無實作任何自動清理機制
3939
@@ -49,9 +49,9 @@ Rust 擁有另一個不安全的自我的另一理由是電腦硬體本質上就
4949
5050注意,這段程式碼並無使用 ` unsafe ` 關鍵字。我們可以在安全程式碼中建立裸指標,我們只是不能在不安全區塊外對其解參考,你很快就會看到。
5151
52- 我們透過 ` as ` 將不可變與可變參考轉型成個別對應的裸指標。由於這些裸指標是從保證合法的參考而來,就能得知這些裸指標同樣合法,但我們無法推導所有裸指標都合法 。
52+ 我們透過 ` as ` 將不可變與可變參考轉型成個別對應的裸指標。由於這些裸指標是從保證有效的參考而來,就能得知這些裸指標同樣有效,但我們無法推導所有裸指標都有效 。
5353
54- 為了展示上述情形,接下來,我們將建立無法確認合法性的裸指標 ,範例 19-2 展示了如何從任意記憶體的位置建立裸指標。嘗試使用任意的記憶體行為並未定義,該位址上可能有也可能沒資料,且編譯器可能會最佳化該程式,所以該處可能不會存取記憶體,或是程式因區段錯誤導致崩潰。一般情況下,雖然這種程式碼能寫得出來,但不會有任何好理由寫出它。
54+ 為了展示上述情形,接下來,我們將建立無法確認有效性的裸指標 ,範例 19-2 展示了如何從任意記憶體的位置建立裸指標。嘗試使用任意的記憶體行為並未定義,該位址上可能有也可能沒資料,且編譯器可能會最佳化該程式,所以該處可能不會存取記憶體,或是程式因區段錯誤導致崩潰。一般情況下,雖然這種程式碼能寫得出來,但不會有任何好理由寫出它。
5555
5656``` rust
5757{{#rustdoc_include .. / listings / ch19 - advanced - features / listing - 19 - 02 / src / main . rs: here }}
@@ -69,7 +69,7 @@ Rust 擁有另一個不安全的自我的另一理由是電腦硬體本質上就
6969
7070建立一個指標沒有危險性,只有當我們嘗試存取它指向的值時,才可能需要處理非法的值。
7171
72- 請注意,範例 19-1 與 19-3,我們建立了 ` *const i32 ` 與 ` *mut i32 ` 兩個裸指標,皆指向相同儲存 ` num ` 的記憶體位置。若我們走正常程序建立指向 ` num ` 的不可變與可變參考,程式碼將因為 Rust 所有權規則不允許同時存在一個可變參考與多個不可變參考,進而無法編譯。有了裸指標,即可建立指向同個位置的可變指標和不可變指標,並透過可變指標改變其資料,但可能帶來資料競爭(data races),請小心!
72+ 請注意,範例 19-1 與 19-3,我們建立了 ` *const i32 ` 與 ` *mut i32 ` 兩個裸指標,皆指向相同儲存 ` num ` 的記憶體位置。若我們走正常程序建立指向 ` num ` 的不可變與可變參考,程式碼將因為 Rust 所有權規則不允許同時存在一個可變參考與多個不可變參考,進而無法編譯。有了裸指標,即可建立指向同個位置的可變指標和不可變指標,並透過可變指標改變其資料,但可能帶來資料競爭(data races),請小心!
7373
7474既然有這些危險,為什麼你還要用裸指標呢?一個主要使用案例是與 C 程式碼介接,你將會在下一節[ 「呼叫不安全函式或方法」] ( #呼叫不安全函式或方法 ) 讀到。另一個用例是在借用檢查器不理解之處建立一層安全抽象。我們將會介紹不安全函式,再探討一個使用到不安全程式碼的安全抽象範例。
7575
@@ -135,9 +135,9 @@ Rust 的借用檢查器(borrow checker)不能理解我們同時借用一個
135135
136136我們判定 ` mid ` 索引在該切片內。此後我們進入不安全程式碼:` slice::from_raw_parts_mut ` 函式需要一個裸指標與一個長度,並建立一個切片。我們使用這個函式來建立一個從 ` ptr ` 開始長度為 ` mid ` 的切片。而後,我們以 ` mid ` 作為引數,對 ` ptr ` 呼叫 ` add ` 方法,以取得從 ` mid ` 開始的裸指標,再來用此指標與從 ` mid ` 開始剩下的元素個數作為長度,建立另一個切片。
137137
138- ` slice::from_raw_parts_mut ` 之所以為不安全函式,是因為它需要裸指標,且必須相信這個指標合法 。` add ` 是不安全方法是由於它必須相信偏移後的位址是合法指標 。因此,我們需要在呼叫 ` slice::from_raw_parts_mut ` 和 ` add ` 外包一層 ` unsafe ` 函式。透過閱讀程式碼與加上對 ` mid ` 一定等於或比 ` len ` 小的斷言,我們可以宣稱所有在 ` unsafe ` 區塊的裸指標都是指向原始切片內的合法指標 。這是一個可接受且合理的 ` unsafe ` 使用情境。
138+ ` slice::from_raw_parts_mut ` 之所以為不安全函式,是因為它需要裸指標,且必須相信這個指標有效 。` add ` 是不安全方法是由於它必須相信偏移後的位址是有效指標 。因此,我們需要在呼叫 ` slice::from_raw_parts_mut ` 和 ` add ` 外包一層 ` unsafe ` 函式。透過閱讀程式碼與加上對 ` mid ` 一定等於或比 ` len ` 小的斷言,我們可以宣稱所有在 ` unsafe ` 區塊的裸指標都是指向原始切片內的有效指標 。這是一個可接受且合理的 ` unsafe ` 使用情境。
139139
140- 注意,我們不需替 ` split_at_mut ` 函式輸出結果做上 ` unsafe ` 的記號,而且我們可以在安全的 Rust 呼叫它。我們藉由安全的方式使用 ` unsafe ` 函式,完成了對不安全程式碼建立一層安全抽象,這個抽象只會從該函式能夠存取的資料內建立合法指標 。
140+ 注意,我們不需替 ` split_at_mut ` 函式輸出結果做上 ` unsafe ` 的記號,而且我們可以在安全的 Rust 呼叫它。我們藉由安全的方式使用 ` unsafe ` 函式,完成了對不安全程式碼建立一層安全抽象,這個抽象只會從該函式能夠存取的資料內建立有效指標 。
141141
142142對比之下,範例 19-7 中使用 ` slice::from_raw_parts_mut ` 則極有可能會在該切片被使用時崩潰。這段程式碼從任意的記憶體位置建立了一個 10,000 元素長的切片。
143143
@@ -147,7 +147,7 @@ Rust 的借用檢查器(borrow checker)不能理解我們同時借用一個
147147
148148<span class =" caption " >範例 19-7:從任意記憶體位址建立切片</span >
149149
150- 我們不擁有此位址之下的記憶體,且並不保證這段程式碼建立的切片一定包含合法的 ` i32 ` 值。嘗試將 ` values ` 當作合法的切片來使用 ,會導致為未定義行為(undefined behavior)。
150+ 我們不擁有此位址之下的記憶體,且並不保證這段程式碼建立的切片一定包含有效的 ` i32 ` 值。嘗試將 ` values ` 當作有效的切片來使用 ,會導致為未定義行為(undefined behavior)。
151151
152152#### 使用 ` extern ` 函式呼叫外部程式碼
153153
0 commit comments