一般的程式語言的變數都有其所屬的作用域(scope,能引用到這個變數的範圍),而在rust中也有,但rust變數可以選擇將變數**借出(borrow)給其他的scope,也可以將變數的所有權整個轉移(move)**出去。而轉移出去的變數別人不還的話是拿不回來的,但借出去的變數可以,因為是暫時給別人使用。
所有權規則:
我們透過String Type來觀察儲存在Heap上的資料,並研究 Rust 是如何知道要清理資料的
為何String可變的,但char卻不行?
兩者最主要的差別在於它們對待記憶體的方式。
( 1 )char在編譯時可以知道它的內容,所以能寫死在最終執行檔。
( 2 )String為了支援可變性,所以需要再Heap上分配一塊編譯時未知大小的記憶體空間來儲存內容。也是就是:
fn main() {
// ::讓我們可以將from函式置於String型別的namespace下
let mut s = String::from("hello"); // s在此開始視為有效
// 使用 s
s.push_str(", world!"); // push_str() 將char加到string後面
println!("{}", s); // print `hello, world!`
} // 此作用域結束 s 不再有效
當s離開scope時,我們就可以很自然地將String所需要的記憶體釋放回分配器。
當變數離開作用域時,Rust 會幫我們呼叫一個特殊函式drop,來釋放記憶體。(Rust 會在大括號結束時自動呼叫 drop)
// 把5賦值給x,然後copy一份x的值給y,並且由於是固定大小的值,所以這x和y的值兩個都會推入heap中
let x = 5;
let y = x;
// 由於string並非固定大小,會透過記憶體分配器來管理記憶體大小。
//
// x透過指標指向"分配的記憶體空間(heap裡)",並記錄長度和容量
// 所以當x賦值給x2時,我們是將x2的指標指向"相同的記憶體空間位置"
// 而不是拷貝整組資料(x2不會自己再請求分配空間,而是參考x)
let x = String::from("hello");
let x2 = x;
let x = String::from("Hello");
let x2 = x;
println!("{}, world!", x);
我們能看到出現錯誤
rust-lang playground
也就是我們把變數給送了出去,離開了我們的scope,所以我們接著無法使用。
為何無法使用?rust防止並避免你進行"無效的引用"操作
當我們的變數離開scope時,Rust會自動呼叫drop來清理該變數在heap上的資料。
而我們知道x和x2是參考同一組data,如果沒有藉由Move機制,會有一個問題,就是x和x2兩個人離開scope:
x變數裡的資料被釋放;x2變數裡的資料被釋放。
兩個人都嘗試釋放相同的記憶體位置(資料區塊),會出現雙重釋放(Double free)的問題,很容易造成重複清除記憶體,導致資料損壞。
所以,我們透過move操作,讓x被移動到x2,來解決問題,來讓只有x2離開scope時,會釋放記憶體。
那如果我們想拷貝整份資料,而不是參考別人的資料的話,那怎麼辦?我們可以透過Clone來達成。Rust有個特別功能叫做Copy trait,能用在標記像是整數這樣存在heap上的型別。
當一個data type有Copy特徵,那麼"在一個變數賦值給其他變數後仍然會是有效的"。
因為他不是轉移所有權,而是透過讓另一個變數自己去申請空間(不跟他人共用,也就是參考)並將變數整個資料拷貝下來給自己使用。
詳細的Copy trait可以去閱讀docs來更加的了解。這邊只簡單介紹
...續
今天簡單介紹Rust中將變數所有權轉移(Move)出去,明天會接續介紹Borrow