Rust 所有權(quán)

認(rèn)識(shí)所有權(quán)

所有權(quán)是 rust獨(dú)特的功能罕拂,它讓 rust無(wú)需垃圾回收即可保證內(nèi)存安全。

什么是所有權(quán)

Rust核心功能之一是所有權(quán)鲜侥。所有運(yùn)行的程序都必須管理其使用的計(jì)算內(nèi)存的方式涯雅。一些語(yǔ)言具有內(nèi)存回收機(jī)制外构,在運(yùn)行時(shí)不斷地尋址不再使用的內(nèi)存帆吻。在另一些語(yǔ)言中域那,程序員必須親自分配和釋放內(nèi)存。Rust則使用第三中方式:通過(guò)所有權(quán)系統(tǒng)管理內(nèi)存猜煮,在編譯時(shí)會(huì)根據(jù)一系列規(guī)則檢查進(jìn)行檢查次员。在運(yùn)行時(shí),所有權(quán)的任何功能不會(huì)減慢程序友瘤。

所有權(quán)規(guī)則

  • Rust中的每一個(gè)值都有一個(gè)被稱為其 所有者的變量
  • 值有且只有一個(gè)所有者
  • 當(dāng)所有者離開作用域翠肘,這個(gè)值將被丟棄

變量作用域

{
  // s 在這里無(wú)效,尚未聲明
  let s = "abc";
  // 使用 s
} // 此作用域已結(jié)束 s 不在有效

內(nèi)存與分配

就字符串字面值來(lái)說(shuō)辫秧,編譯時(shí)就知道其內(nèi)容,所以文本被直接硬編碼到最終可執(zhí)行文件被丧。這使得字盟戏,值快速且高效。這是因?yàn)樽置嬷档牟豢勺冃陨稹2恍业氖鞘辆浚覀儾荒転榱嗣恳粋€(gè)在編譯時(shí)大小未知的文本而將一塊內(nèi)存放入二進(jìn)制文件中,并且它的大小可能隨著程序運(yùn)行發(fā)生變化黄选。

對(duì)于 String類型蝇摸,為了支持一個(gè)可變婶肩,可增長(zhǎng)的文本片段,需要在堆上分配一塊在編譯時(shí)未知大小的內(nèi)存來(lái)存放內(nèi)容貌夕。
這意味著:

  • 必須在運(yùn)行時(shí)向操作系統(tǒng)請(qǐng)求內(nèi)存律歼。
  • 需要一個(gè)當(dāng)我們處理完String時(shí)將內(nèi)存返回給操作系統(tǒng)的方法。
    第一部分:當(dāng)調(diào)用 String::from 時(shí)啡专,它實(shí)現(xiàn)了請(qǐng)求所需內(nèi)存险毁。
    第二部分:內(nèi)存在擁有它的變量離開作用域時(shí)就被自動(dòng)釋放。
{
  let s = String::from("abc");
  // 使用 s
} // 此作用域結(jié)束 s 不再有效

當(dāng)變量離開作用域時(shí)们童,Rust給我們調(diào)用了一個(gè)特殊的 drop函數(shù)畔况。

變量與數(shù)據(jù)交互的方式(一):移動(dòng)

let x = 5;
let y = x;

這里做了什么:將 5綁定到 x, x拷貝到y。現(xiàn)在 xy都等于 5慧库。因?yàn)槭且阎墓潭ù笮〉闹吊喂颍詢蓚€(gè) 5被放入到了棧中。

現(xiàn)在看看String的版本:

let s1 = String::from("abc");
let s2 = s1;

看起來(lái)和上面的代碼非常相識(shí)齐板,現(xiàn)在假設(shè)和他們的運(yùn)行方式相識(shí):s1 拷貝到 s2吵瞻。不過(guò)事實(shí)上完全不是這樣。
String::from在堆內(nèi)存申請(qǐng)了空間覆积,s1指向了申請(qǐng)的內(nèi)存听皿。let s2 = s1只是 s2拷貝了s1指向的內(nèi)存地址,長(zhǎng)度和容量宽档,并沒(méi)有復(fù)制堆上的數(shù)據(jù)尉姨。如果Rust也復(fù)制了堆上的數(shù)據(jù),那么會(huì)對(duì)運(yùn)行時(shí)的性能造成非常大的影響吗冤。

之前提到過(guò)變量離開作用域后會(huì)自動(dòng)調(diào)用drop函數(shù)并清理堆內(nèi)存又厉。這里s1s2都指向同一堆內(nèi)存地址。當(dāng)s1s2離開作用域時(shí)椎瘟,他們會(huì)釋放相同的內(nèi)存覆致,這可能是一個(gè)二次釋放的錯(cuò)誤,兩次釋放相同內(nèi)存會(huì)導(dǎo)致內(nèi)存污染肺蔚,它肯能會(huì)導(dǎo)致潛在的安全漏洞煌妈。

為了確保內(nèi)存安全,這種場(chǎng)景下Rust的處理有另一個(gè)細(xì)節(jié)值得注意宣羊。與其拷貝分配的內(nèi)存璧诵,Rust則認(rèn)為s1不再有效,在s1離開作用域后不需要清理任何東西仇冯,在s2創(chuàng)建只后s1也無(wú)法使用了之宿。嘗試編譯會(huì)得到一個(gè)錯(cuò)誤:

error[E0382]: borrow of moved value: `s1`
 --> .\rust所有權(quán).rs:4:20
  |
2 |     let s1 = String::from("abc");
  |         -- move occurs because `s1` has type `std::string::String`, which does not implement the `Copy` trait
3 |     let s2 = s1;
  |              -- value moved here
4 |     println!("{}", s1);
  |                    ^^ value borrowed here after move

error: aborting due to previous error

如果你在其他語(yǔ)言聽說(shuō)過(guò)淺拷貝深拷貝,這看起來(lái)像淺拷貝。不過(guò)因?yàn)?code>Rust使第一個(gè)變量無(wú)效了苛坚,這操作被成為移動(dòng)比被。

變量與數(shù)據(jù)交互的方式(二):克隆

如果我們確實(shí)需要深度復(fù)制String中堆上的數(shù)據(jù)色难。而不僅僅是棧上的數(shù)據(jù),可以用一個(gè)叫做clone的通用函數(shù)等缀。

let s1 = String::from("hello");
let s2 = s1.clone();

println!("s1 = {}, s2 = {}", s1, s2);

這段代碼能正常運(yùn)行枷莉,這里堆上的數(shù)據(jù)復(fù)制了

只在棧上的數(shù)據(jù):拷貝

let x = 5;
let y = x;

println!("x = {}, y = {}", x, y);

這段代碼似乎和我們剛剛學(xué)到的矛:沒(méi)有使用 clone,不過(guò)依然有效且沒(méi)有被移動(dòng)y中项滑。

原因是像整形這樣的在編譯時(shí)已知大小的類型被整個(gè)儲(chǔ)存在棧上依沮,所以拷貝的值是快速的。這也意味著沒(méi)有理由在創(chuàng)建 y后使 x無(wú)效枪狂。換句話說(shuō)這里的深拷貝淺拷貝沒(méi)有什么不同危喉。

Rust 有一個(gè)叫做 Copy trait 的特殊注解,可以用在類似整型這樣的存儲(chǔ)在棧上的類型州疾。
一些Copy的類型:

  • 所以的整數(shù)類型辜限,比如 u32
  • 布爾類型, bool,它的值是truefalse
  • 所有的浮點(diǎn)類型,比如 f64
  • 字符類型, char薄嫡。
  • 元組颗胡,當(dāng)且僅當(dāng)其包含的類型都是 Copy的時(shí)候。比如(i32, i32)哑蔫,但(i32, String)就不是弧呐。

所有權(quán)與函數(shù)

將值傳遞給函數(shù)在語(yǔ)義上與給變量賦值相似。向函數(shù)傳遞值可能會(huì)移動(dòng)或復(fù)制腥沽,就像復(fù)制語(yǔ)句一樣今阳。

fn main() {
  let s = String::from("abc");
  f1(s); // s 移動(dòng)到了 f1
  // 這里 s 不再有效
   let x = 5;
   f2(x); // 這里 copy 了x,x 還是可以使用
} // 這里 x 先移出了作用域酣栈,然后是 s汹押。因?yàn)?s 的值已被移走棚贾,這里不會(huì)有特殊操作

fn f1(s: String) {
  println!("{}", s);
} // 這里 s 移出了作用域,并調(diào)用 drop 方法铸史。占用的內(nèi)存被釋放

fn f2(x: i32) {
  println!("{}", x);
} // 這里 x 移出了作用域怯伊,不會(huì)有特殊操作

返回值與作用域

fn main() {
    let s1 = f1(); // f1() 將返回值 移給 s1
    let s2 = String::from("hello"); // s2 進(jìn)入作用域
    let s3 = f2(s2); // s2 被移動(dòng)到 f2 中,它也將返回值移給 s3
} // 這里 s3 移出作用域崭篡,調(diào)用 drop琉闪,s2 移出作用域,但已被移走不會(huì)做任何操作颠毙。s1 移出作用域調(diào)用 drop

fn f1() -> String {// 將返回值移動(dòng)給調(diào)用方
    String::from("abc")
}

fn f2(s: String) -> String {// s 進(jìn)入作用域
    s // 返回 s 并移出作用域給調(diào)用方
}

在一個(gè)函數(shù)中都獲取作用域蛀蜜,并接著返回所有權(quán)有些啰嗦增蹭。如果函數(shù)使用一個(gè)值但不獲取所有權(quán)該怎么辦呢?Rust有一個(gè)功能叫做引用

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末沪铭,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子椰憋,更是在濱河造成了極大的恐慌橙依,老刑警劉巖硕旗,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異创译,居然都是意外死亡软族,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門掖疮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)颗祝,“玉大人,你說(shuō)我怎么就攤上這事搁宾∥虑停” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵奸忽,是天一觀的道長(zhǎng)栗菜。 經(jīng)常有香客問(wèn)我蹄梢,道長(zhǎng),這世上最難降的妖魔是什么禁炒? 我笑而不...
    開封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任幕袱,我火速辦了婚禮,結(jié)果婚禮上涯捻,老公的妹妹穿的比我還像新娘望迎。我一直安慰自己辩尊,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開白布蝗拿。 她就那樣靜靜地躺著,像睡著了一般哀托。 火紅的嫁衣襯著肌膚如雪仓手。 梳的紋絲不亂的頭發(fā)上玻淑,一...
    開封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天补履,我揣著相機(jī)與錄音箫锤,去河邊找鬼。 笑死谚攒,一個(gè)胖子當(dāng)著我的面吹牛馏臭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播绕沈,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼乍狐,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼澜躺!你這毒婦竟也來(lái)了抒蚜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤操漠,失蹤者是張志新(化名)和其女友劉穎浊伙,沒(méi)想到半個(gè)月后撞秋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吻贿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了帐要。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖盛霎,靈堂內(nèi)的尸體忽然破棺而出摩渺,到底是詐尸還是另有隱情摇幻,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布绰姻,位于F島的核電站枉侧,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏狂芋。R本人自食惡果不足惜榨馁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望帜矾。 院中可真熱鬧翼虫,春花似錦、人聲如沸屡萤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)死陆。三九已至招拙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背别凤。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留注祖,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像层扶,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容