初識Rust語言的所有權概念
目前僅看了第二版的官方文檔,記錄一下初步印象,應該還有更深刻一致的解釋,水平有限,僅供參考。
實驗環(huán)境:ubuntu17.10,rust1.18,vscode1.14 + 擴展rust(rls)。
BTW,環(huán)境搭建順利得令人意外,Rust工具鏈打造的簡潔精美,原生支持git,安裝只需一條命令:curl https://sh.rustup.rs -sSf | sh。
初步印象
數(shù)據(jù)競爭主要有三個條件:
- 兩個或更多指針同時訪問同⼀數(shù)據(jù)。
- ⾄少有⼀個指針被寫⼊。
- 沒有同步數(shù)據(jù)訪問的機制。
R非常重視并發(fā),根據(jù)官方介紹:Rust 是一門著眼于安全、速度和并發(fā)的編程語言。而并發(fā)需要解決的就是數(shù)據(jù)競爭問題,自然會非常重視數(shù)據(jù)的使用過程,說是小心翼翼不為過。因為數(shù)據(jù)要關聯(lián)到有名變量才能使用,所以rust在語言層面上針對變量的使用引入了解決方法,主要涉及的語法有:
- 變量聲明時,不可變(immutable,默認)、可變(mutable)
- 變量賦值時,所有權轉移(move)、借用(borrow)
需要注意的是,所有權僅針對復雜類型變量(在語法上,是沒有copy trait的類型),例如String、vect等在堆上存儲數(shù)據(jù)的類型,而簡單類型并不用考慮,如int、tuple、array等,原因就在于賦值時數(shù)據(jù)是如何拷貝的(雖然都是淺拷貝)。
如果熟悉淺拷貝、深拷貝的概念,自然了解,對于在堆上分配空間的復雜類型,淺拷貝會導致兩個或更多變量/指針同時指向同⼀數(shù)據(jù),若有變量/指針作寫入操作,就會引起數(shù)據(jù)競爭問題。
所以,Rust用可變/不可變、所有權、生命期等來破壞數(shù)據(jù)競爭的條件,而這些解決方案全部在編譯期搞定!
當然,代價是難以快速驗證想法,畢竟使用變量時要仔細了,否則編都編不過,期待***實踐和IDE的支持。
基本概念
1. 不可變、可變
let x = 3; // x 默認不可變 x = 4; // 錯誤! let x = 4; // 正確!遮蓋了原有的同名變量 let mut y = 3; // y可變 y = 4; // 正確!
2. 所有權轉移(move)
fn test(v: String) { println!("fn: {}", v); } // 函數(shù)
let x = String::from("hello"); // 所有者x(String類型)
let y = x; // 數(shù)據(jù)的所有權轉移給y!
let z = x; // 錯誤!x已不可用
test(y); // 所有權轉移,新的所有者是形參v!當函數(shù)執(zhí)行完畢,v離開作用域時值被丟棄(drop)!
println!("var: {}", y); // 錯誤!y已不可用
這難免有令人抓狂的感覺,還能不能愉快地玩耍了?這數(shù)據(jù)跑得跟兔子一樣,想用的時候都不知道去哪了!還可能無意中跑到函數(shù)里直接躺尸!
3. 借用/引用(borrow)
那么,一個變量想多次使用怎么辦?答案是可以借用:使⽤其值但不獲取其所有權。
fn test1(v: String) { println!("fn: {}", v); }
fn test2(v: &String) { println!("fn: {}", v); } // 參數(shù)為引用類型
let s = String::from("hello"); // 所有者s(String類型)
let s1 = &s; // 不可變借用(borrow)!
let s2 = &s; // 借用
let s3 = s1; // 借用
test2(s1); // 借用
test1(*s1); // 錯誤!借用者s1沒有所有權,無法通過s1轉移(cannot move out of borrowed content)。
println!("var: {}", s); // 正確
小結:個人感覺,所有權轉移主要為并發(fā)服務,本身并不常用,畢竟數(shù)據(jù)經(jīng)常要復用,沒人樂意要一直提防著數(shù)據(jù)跑哪去了,尤其在函數(shù)調用時。既然如此,一般把所有者保持不變,多使用引用,主要體現(xiàn)在復雜數(shù)據(jù)結構和函數(shù)上。
進一步
但是,實際使用的情況會比較復雜,即是否可變與轉移、借用三者相互影響(混用)的情況。
從數(shù)據(jù)競爭的角度:讀讀不沖突,但讀寫、寫寫會沖突(讀即不可變,寫即可變);從實現(xiàn)的角度:引用是基于所有權的。
因此,可以看看哪些對象會沖突:(所有者,引用) × (不可變,可變)
首先,是否可變和所有權沒有關系。
let x = String::from("hello");
let mut z = x; // 轉移后變量x不可用
z.push_str(" z"); //正確
// 可變引用要用星號來獲得引用的內容,不可變引用不需要。
let mut x = 5;
let y = &mut x;
*y += 1;
雖然不可變引用(&T)沒有所有權,不會導致值被誤轉移,但借用之時要求值不能變,這意味著此時:所有權不能轉移、所有者不能改值、不能同時有可變引用!
let mut x = String::from("hello");
let y = &x; // 不可變引用
let z = x; // 錯誤
x.push_str(" x"); // 錯誤
let z = &mut x; // 錯誤:可變引用
可變引用(&mut T)
可變引用使用上略復雜,概念上也沒有太統(tǒng)一的理解,這里單獨考查。
“可變權”即可變引用對數(shù)據(jù)的讀寫權,具有唯一性(只有一個可用的可變引用)和獨占性(其它讀、寫統(tǒng)統(tǒng)無效),所以對編譯影響相當大。可變引用的可變權和所有者對數(shù)據(jù)的所有權有相似性,因為可變權也有move行為。
注:官方文檔里沒有可變權的概念,但個人感覺,用這個概念比較好理解可變引用的使用,也許還有更本質的解釋,特此說明。
可變權move的兩種方式
let mut x = String::from("hello"); // 所有者x有可變權
// 1. 直接轉移
let y = &mut x; // 1. y為可變引用,可變權move自x
let z = y; // 直接轉移。z為可變引用
y.push_str(" y"); // 錯誤!y的可變權已move給z
z.push_str(" z"); // 正確
// 2. 間接轉移
let mut y = &mut x; // 2. y為可變引用,可變權move自x
let w = &mut y; // 要求y可變。w為可變引用
w.push_str(" w"); // 正確
// 轉移(函數(shù))
fn test(i: &mut String) {
i.push_str(" i"); // 正確
}
let mut x = String::from("hello"); // 所有者x有可變權
test(&mut x);
x.push_str(" x"); // 正確!可變權已歸還
可變引用若有寫入操作則要求所有者可變。
let x = String::from("hello"); // x不可變
let mut z = &x; // z為不可變引用
z.push_str(" z"); // 錯誤!
let w = &mut z; // w為可變引用
w.push_str(" w"); // 錯誤!
let mut y = x; // 所有權轉移,y可變
let z = &mut y; // z為可變引用,要求y可變
z.push_str(" z"); // 正確!
let w = &z; // w 為不可變引用
w.push_str(" w"); // 錯誤!
總結:
因為都涉及到值的修改,可變引用的行為和所有者相似,而且可變權和所有權都是面向數(shù)據(jù)且唯一的。
所有者
- 有所有權,move后不再可用,當所有者生命期結束,值被丟棄。
- 讀的時候類似不可變引用,寫的時候類似可變引用。
可變引用(&mut T)
- 有可變權,move自被引用者,當可變引用生命期結束,可變權自動歸還。
- 可變權的源頭應該來自所有者,否則意義不大。




























