文 | 1724字 — 估計閱讀 | 6分鐘web
任何事情都是相對的,就像Rust給咱們的印象一直是安全、快速,但實際上,徹底的安全是不可能實現的。所以,Rust中也是會有不安全的代碼的。編程
嚴格來說,Rust語言能夠分爲Safe Rust和Unsafe Rust。Unsafe Rust是Safe Rust的超集。安全
在Unsafe Rust中並不會禁用任何的安全檢查,Unsafe Rust出現的緣由是爲了讓開發者能夠作一些更加底層的操做。微信
這些事情自己也是不安全的,若是仍然要進行Rust的安全檢查,那麼就沒法進行這些操做。多線程
在進行下面這5種操做時,Unsafe Rust不會進行安全檢查。編輯器
解引用原生指針函數
調用unsafe的函數或方法flex
訪問或修改可變的靜態變量spa
實現unsafe的trait.net
讀寫聯合體中的字段
基礎語法
Unsafe Rust的關鍵字是unsafe,它能夠用來修飾函數、方法和trait,也能夠用來標記代碼塊。
標準庫中也有很多函數是unsafe的。例如String中的from_utf8_unchecked()函數。
它的定義以下:
pub unsafe fn from_utf8_unchecked(bytes: Vec<u8>) -> String {
String { vec: bytes }
}
這個函數被標記爲unsafe的緣由是函數並無檢查傳入參數是不是合法的UTF-8序列。也就是提醒使用者注意,使用這個函數要本身保證參數的合法性。
用unsafe標記的trait也比較常見,在前面咱們見過的Send和Sync都是unsafe的trait。
它們被用來保證線程安全, 將其標記爲unsafe是告訴開發者,若是本身實現這兩個trait,那麼代碼就會有安全風險。
咱們在調用unsafe函數或方法時,須要使用unsafe代碼塊。
fn main() {
let sparkle_heart = vec![240, 159, 146, 150];
let sparkle_heart = unsafe {
String::from_utf8_unchecked(sparkle_heart)
};
assert_eq!("�", sparkle_heart);
}
在瞭解了unsafe的基礎語法以後,咱們再來具體看看前面提到的5種操做。
解引用原生指針
Rust的原生指針分爲兩種:可變類型*mut T和不可變類型*const T。
與引用和智能指針不一樣,原生指針具備如下特性:
能夠不遵循借用規則,在同一代碼塊中能夠同時出現可變和不可變指針,也能夠同時有多個可變指針
不保證指向有效內存
容許是null
不會自動清理內存
由這些特性能夠看出,原生指針並不受Rust那一套安全規則的限制,所以,解引用原生指針是一種不安全的操做。
換句話說,咱們應該把這種操做放在unsafe代碼塊中。
下面這段代碼就展現了原生指針的第一條特性,以及如何解引用原生指針。
fn main() {
let mut num = 5;
let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;
unsafe {
println!("r1 is: {}", *r1);
println!("r2 is: {}", *r2);
}
}
在Rust編程中,原生指針常被用做和C語言打交道,原生指針有一些特有的方法,例如能夠用is_null()來判斷原生指針是不是空指針,用offset()來獲取指定偏移量的內存地址的內容,使用read()/write()方法來讀寫內存等。
調用unsafe的函數或方法
調用unsafe的函數或方法必須放到unsafe代碼塊中,這點咱們在基礎知識中已經介紹過。由於函數自己被標記爲unsafe,也就意味着調用它可能存在風險。這點無需贅述。
訪問或修改可變的靜態變量
對於不可變的靜態變量,咱們訪問它不會存在任何安全問題,可是對於可變的靜態變量而言,若是咱們在多線程中都訪問同一個變量,那麼就會形成數據競爭。
這固然也是一種不安全的操做。因此要放到unsafe代碼塊中,此時線程安全應由開發者本身來保證。
static mut COUNTER: u32 = 0;
fn add_to_count(inc: u32) {
unsafe {
COUNTER += inc;
}
}
fn main() {
add_to_count(3);
unsafe {
println!("COUNTER: {}", COUNTER);
}
}
在這個例子中咱們沒有使用多線程,這裏只是想展現一下如何訪問和修改可變靜態變量。
實現unsafe的trait
當trait中包含一個或多個編譯器沒法驗證其安全性的方法時,這個trait就必須被標記爲unsafe。而想要實現unsafe的trait,首先在實現代碼塊的關鍵字impl前也要加上unsafe標記。
其次,沒法被編譯器驗證安全性的方法,其安全性必須由開發者本身來保證。
前面咱們也提到了,常見的unsafe的trait有Send和Sync這兩個。
讀寫聯合體中的字段
Rust中的Union聯合體和Enum類似。咱們可使用union關鍵字來定義一個聯合體。
union MyUnion {
i: i32,
f: f32,
}
fn main() {
let my_union = MyUnion{i: 3};
unsafe {
println!("{}", my_union.i);
}
}
在初始化時,咱們每次只能指定一個字段的值。這就形成咱們在訪問聯合體中的字段時,有可能會訪問到未定義的字段。
所以,Rust讓咱們把訪問操做放到unsafe代碼塊中,以此來警示咱們必須本身保證程序的安全性。
總結
本文咱們聊了Unsafe Rust的一些使用場景和使用方法。你只須要記住Unsafe的5種操做就好,在遇到這些操做時,必定要使用unsafe代碼塊。
unsafe代碼塊不光是爲了「騙」過編譯器,要時刻提醒本身,unsafe代碼塊中的程序要由開發者本身保證其正確性。
解引用原生指針
調用unsafe的函數或方法
訪問或修改可變的靜態變量
實現unsafe的trait
讀寫聯合體中的字段
本文分享自微信公衆號 - 代碼潔癖患者(Jackeyzhe2018)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。