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