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