這篇主要參考是:
http://slides.com/liigo/rust-memory/fullscreen#/
前言:
問題是什麼呢?Pointer 讓你直接操控一塊記憶體,相對的,它也會造成空的指標,雙重釋放、未釋放記憶體等不安全(這裡的安全是指不正常的使用指標,造成memory leak)的操作;兩個指標可以指向同一塊記憶體,在多執行緖裡造成race condition(競態條件)。
我們既需要pointer 所帶來的彈性,又不希望pointer 和不正常使用帶來的不安全。
同時當系統愈來愈大,加上更多平行化機制之後,要求設計師對每塊宣告的記憶體負責愈顯得不切實際,作為下一代的語言實在不該讓程式莫名的存取到不該用的記憶體,讓作業系統丟出seg fault 把程式切掉(引用:AZ大神)。
類似的問題與回應在Cpp也看得到(我真懷疑Cpp到底有什麼概念沒實作的XDDDD),像是 C++11引入的smart pointer, shared pointer,都是針對這個問題而來。
Rust 使用的解法是:定義了Rust Ownership跟Lifetime規則,直接限制pointer 的傳遞、複製、刪除,由編譯器在編譯時即進行檢查(老實說還滿嚴格的 Jizz),避免不正當的pointer 使用方式,來兼顧安全與控制;同時排除掉執行期的檢查成本。
相對於golang,使用的是garbage collection 的方式來處理資源釋放的問題,Rust 連garbage collection 都不要有,一但你的資源(memory) 生命期結束,編譯器就自動幫你把資源釋放掉。
Pointer, Ownership, Lifetime:
Ref pointer: http://rustbyexample.com/flow_control/match/destructuring/destructure_pointers.html
Box: http://rustbyexample.com/box.html
我們這裡提到兩個名詞:持有權(Ownership)跟生命期(Lifetime),就來做細部介紹:
Ownership指的是一個pointer 對某塊記憶體的持有權。
例如在ownership guide 裡的例子,在一個scope 裡面malloc/free 一塊記憶體,在Rust 裡就是直接宣告let x = box; Rust compiler 會幫你放掉這塊記憶體。
所有的資源都只能有一個持有者,擁有持有權的pointer 可以將pointer 轉送(Move)或借給(Borrow)其他人,例如把它傳送到函數裡面,當我們把pointer 當做函數的argument時,資源擁有權就轉走了。
例如當我們用box pointer:也就是C裡面的malloc/free 型態的pointer,傳送到function 裡的時候,在caller 的變數即無法再存取這塊資料,在函式的末端,這個資源的持有權無人承接,系統就將它釋放掉,就像這樣:
如果要再使用這塊記憶體,就要在函數末端將它的擁有權傳回來
The Rust Ownership Guide
http://smallcultfollowing.com/rust-int-variations/imem-umem/guide-ownership.html
Rust by example: 17-19
http://rustbyexample.com/
例如在ownership guide 裡的例子,在一個scope 裡面malloc/free 一塊記憶體,在Rust 裡就是直接宣告let x = box; Rust compiler 會幫你放掉這塊記憶體。
所有的資源都只能有一個持有者,擁有持有權的pointer 可以將pointer 轉送(Move)或借給(Borrow)其他人,例如把它傳送到函數裡面,當我們把pointer 當做函數的argument時,資源擁有權就轉走了。
例如當我們用box pointer:也就是C裡面的malloc/free 型態的pointer,傳送到function 裡的時候,在caller 的變數即無法再存取這塊資料,在函式的末端,這個資源的持有權無人承接,系統就將它釋放掉,就像這樣:
fn main() { let x = Box::new(5); add_one(x); println!("{}", x); } fn add_one(mut num: Box<i32>) { *num += 1; }上面的println 會出現compile error,因為資源已經轉手給add_one,並在add_one 的末端被釋放掉,由於main裡的pointer 已經無法再存取那塊記憶體,由此杜絕存取dangling pointer 的可能性。
如果要再使用這塊記憶體,就要在函數末端將它的擁有權傳回來
fn add_one(mut num: Box<i32>) -> Box<i32> { *num += 1; num }這個寫法相當常見,在Rust 裡可以用ref 的寫法來代替,這在文件內稱為借用(Borrowing),就像這樣:
fn add_one(num: &mut i32) { *num += 1; }那Lifetime呢?在Rust 裡每個變數都有它的Lifetime,一般來說Rust compile 會幫你把這些都管好,你想寫明的時候才用<'name>來指明。下列兩者其實是等價,只是一個寫明Lifetime 的名字:
fn add_one<'a>(num: &'a int) -> int { *num + 1 } fn add_one(num: & int) -> int { *num + 1 }同時Lifetime 的範圍其實也沒那麼不好懂,大抵上就是scope,一但資源出了scope,未使用的就會被釋放掉。
結語:
Pointer, ownership, lifetime 其實是Rust 不太好搞懂的觀念,其實它就是由編譯器進行嚴格檢查,限制不當使用的C pointer,我的感想是,一但設計師適應這樣嚴格檢查,在程式設計階段自然就排除掉C/C++高自由度帶來的那些:可以但是不應該這樣寫的不安全寫法了。參考資料:
更多內容請見The Rust Ownership Guide
Rust by example: 17-19
http://rustbyexample.com/
沒有留言:
張貼留言