任何事情都是相對的,就像Rust給咱們的印象一直是安全、快速,但實際上,徹底的安全是不可能實現的。所以,Rust中也是會有不安全的代碼的。 編程
嚴格來說,Rust語言能夠分爲Safe Rust和Unsafe Rust。Unsafe Rust是Safe Rust的超集。在Unsafe Rust中並不會禁用任何的安全檢查,Unsafe Rust出現的緣由是爲了讓開發者能夠作一些更加底層的操做。這些事情自己也是不安全的,若是仍然要進行Rust的安全檢查,那麼就沒法進行這些操做。安全
在進行下面這5種操做時,Unsafe Rust不會進行安全檢查。多線程
Unsafe Rust的關鍵字是unsafe,它能夠用來修飾函數、方法和trait,也能夠用來標記代碼塊。函數
標準庫中也有很多函數是unsafe的。例如String中的from_utf8_unchecked()
函數。它的定義以下:spa
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代碼塊。code
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
。開發
與引用和智能指針不一樣,原生指針具備如下特性:
由這些特性能夠看出,原生指針並不受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代碼塊中,此時線程安全應由開發者本身來保證。
static mut COUNTER: u32 = 0; fn add_to_count(inc: u32) { unsafe { COUNTER += inc; } } fn main() { add_to_count(3); unsafe { println!("COUNTER: {}", COUNTER); } }
在這個例子中咱們沒有使用多線程,這裏只是想展現一下如何訪問和修改可變靜態變量。
當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代碼塊中的程序要由開發者本身保證其正確性。