Rust?所有權(quán)機(jī)制原理深入剖析
what's ownership?
常見的高級(jí)語言都有自己的 Garbage Collection(GC)機(jī)制來管理程序運(yùn)行的內(nèi)存,例如 Java、Go 等。而 Rust 引入了一種全新的內(nèi)存管理機(jī)制,就是 ownership(所有權(quán))。它在編譯時(shí)就能夠保證內(nèi)存安全,而不需要 GC 來進(jìn)行運(yùn)行時(shí)的內(nèi)存回收。
在 Rust 中 ownership 有以下幾個(gè)規(guī)則:
- 每個(gè)值都有一個(gè) woner(所有者)
- 在同一時(shí)間,每個(gè)值只能有一個(gè) owner
- 當(dāng) owner 離開作用域,這個(gè)值就會(huì)被丟棄
Scope (作用域)
通過作用域來劃分 owner 的生命周期,作用域是一段代碼的范圍,例如函數(shù)體、代碼塊、if 語句等。當(dāng) owner 離開作用域,這個(gè)值就會(huì)被丟棄。
example:
fn main() { let s = String::from("hello"); // 變量 s 進(jìn)入作用域,分配內(nèi)存 // s 在這里可用 } // 函數(shù)體結(jié)束,變量 s 離開作用域,s 被丟棄,內(nèi)存被回收
ownership transfer(所有權(quán)轉(zhuǎn)移)
和大多數(shù)語言一樣,Rust 在棧上分配基本類型的值,例如整型、浮點(diǎn)型、布爾型等。而在堆上分配復(fù)雜類型的值,例如 String、Vec 等。所以,這里就引入了兩個(gè)概念,move
和 clone
。
move
move
操作會(huì)將變量的所有權(quán)轉(zhuǎn)移給另一個(gè)變量,這樣原來的變量就不能再使用了。這里需要注意的是,move
操作只會(huì)發(fā)生在棧上的值,因?yàn)樵诙焉系闹凳遣豢蓮?fù)制的,所以只能通過 clone
操作來復(fù)制。
example:
fn main(){ let s1 = String::from("hello"); let s2 = s1; print!("s1 = {}, s2 = {}", s1, s2); }
在上面的代碼例子中,如果你執(zhí)行就會(huì)在編譯時(shí)報(bào)錯(cuò):
--> src/main.rs:11:32 | 9 | let s1 = String::from("hello"); | -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait 10 | let s2 = s1; | -- value moved here 11 | print!("s1 = {}, s2 = {}", s1, s2); | ^^ value borrowed here after move
編譯器提示我們,s1
在賦值給 s2
時(shí)發(fā)生了 move
的操作,它把字符串 hello
的所有權(quán)移交給了 s2
,此時(shí) s1
的作用域到這里就結(jié)束了,所以后面再使用 s1
就會(huì)報(bào)錯(cuò)。
clone
clone
操作會(huì)將變量的值復(fù)制一份,這樣原來的變量和新的變量就都可以使用了。
這里需要注意的是,clone
操作只會(huì)發(fā)生在堆上的值,因?yàn)樵跅I系闹凳强蓮?fù)制的,所以只能通過 move
操作來轉(zhuǎn)移所有權(quán)。
example:
fn main(){ let s1 = String::from("hello"); let s2 = s1.clone(); print!("s1 = {}, s2 = {}", s1, s2); }
我們對(duì) s1
進(jìn)行 clone
操作,這樣 s1
和 s2
都可以使用了,而且 s1
的所有權(quán)也沒有被轉(zhuǎn)移,所以后面還可以繼續(xù)使用 s1
。
copy
如果一個(gè)類型實(shí)現(xiàn)了 copy
這個(gè) trait,使用它的變量不會(huì)移動(dòng),而是被簡(jiǎn)單地復(fù)制,使它們?cè)诜峙浣o另一個(gè)變量后仍然有效。
example:
fn main() { let x = 5; let y = x; print!("x = {}, y = {}", x, y); }
當(dāng) x
賦值給 y
后,x
和 y
都可以使用,而且 x
的所有權(quán)也沒有被轉(zhuǎn)移,所以后面還可以繼續(xù)使用 x
。這是因?yàn)?i32
這個(gè)類型實(shí)現(xiàn)了 copy
這個(gè) trait,所以 x
的值被復(fù)制了一份,所以 x
和 y
都可以使用。
以下這些數(shù)據(jù)類型實(shí)現(xiàn)了 copy
這個(gè) trait:
- 所有的整數(shù)類型,例如:
u32
、i32
。 - 布爾類型,
bool
,有true
和false
兩個(gè)值。 - 所有的浮點(diǎn)數(shù)類型,例如:
f64
、f32
。 - 字符類型,
char
。 - 元組,當(dāng)且僅當(dāng)它們的元素類型都實(shí)現(xiàn)了
copy
這個(gè) trait。例如,(i32, i32)
實(shí)現(xiàn)了copy
,但是(i32, String)
就沒有實(shí)現(xiàn)。
References and Borrowing(引用和借用)
我們將創(chuàng)建引用的動(dòng)作稱為借用。就像在現(xiàn)實(shí)生活中一樣,如果一個(gè)人擁有某樣?xùn)|西,你可以向他們借用。完成后,您必須將其歸還。你不擁有它。 引用有以下幾個(gè)規(guī)則:
- 在任何給定時(shí)間,你可以擁有任意數(shù)量的引用,但是只能擁有一個(gè)可變引用。
- 引用必須總是有效的。
example1:
fn main() { let s1 = String::from("hello"); let len = calculate_length(&s1); println!("The length of '{}' is {}.", s1, len); } fn calculate_length(s: &String) -> usize { s.len() } // s 作用域失效,但是由于 s 是一個(gè)引用,沒有所有權(quán),所以不會(huì)發(fā)生任何事情
上面代碼中,我們使用符號(hào) &
來創(chuàng)造一個(gè)變量的引用。這里我們使用 &s1
來把這個(gè)引用指向 s1
。函數(shù) calculate_length
的參數(shù) s
的類型是 &String
,這意味著它是一個(gè)指向 String
類型的引用,然后在函數(shù)體內(nèi)獲取 s
的長(zhǎng)度并返回給調(diào)用者。
example2:
fn main(){ // 同一時(shí)間可以擁有多個(gè)不可變引用 let s1 = String::from("hello"); let s2 = &s1; let s3 = &s1; println!("s1 = {}, s2 = {}, s3 = {}", s1, s2, s3); }
Mutable References(可變引用)
可變引用指的是可以改變引用值的引用。在同一作用域中,同一時(shí)間只能有一個(gè)可變引用。
example:
fn main(){ let mut s = String::from("hello"); change(&mut s); println!("{}", s); } fn change(some_string: &mut String) { some_string.push_str(", world"); }
上面代碼中,我們用 mut
先創(chuàng)建了一個(gè)可變變量 s
,然后使用 &mut s
創(chuàng)建了一個(gè)指向 s
的可變引用。函數(shù) change
的入?yún)⒁彩且粋€(gè)指向 String
類型的可變引用,這樣我們就可以在函數(shù) change
中改變 s
的值了。
example2:
fn main() { let mut s = String::from("hello"); let r1 = &mut s; let r2 = &mut s; // 在這里。編譯器會(huì)報(bào)錯(cuò),因?yàn)樵谕蛔饔糜蛑?,同一時(shí)間只能有一個(gè)可變引用。 println!("{}, {}", r1, r2); }
--> src/main.rs:41:14 | 40 | let r1 = &mut s; | ------ first mutable borrow occurs here 41 | let r2 = &mut s; | ^^^^^^ second mutable borrow occurs here 42 | 43 | println!("{}, {}", r1, r2); | -- first borrow later used here
Dangling References(懸垂引用)
懸垂引用是指引用一個(gè)不存在的值。在 Rust 中,這是不可能的,因?yàn)榫幾g器會(huì)在編譯時(shí)就檢查這種情況。下面是一個(gè)例子:
fn main() { let reference_to_nothing = dangle(); // 獲得一個(gè)指向不存在值的引用 } fn dangle() -> &String { let s = String::from("hello"); // s 進(jìn)入作用域 &s // 返回 s 的引用 } // s 作用域結(jié)束,s 被丟棄,內(nèi)存被釋放
--> src/main.rs:51:16 | 51 | fn dangle() -> &String { | ^ expected named lifetime parameter
因?yàn)樽兞?s
的作用域只在 dangle
函數(shù)內(nèi),當(dāng) dangle
函數(shù)返回 s
的引用時(shí),s
已經(jīng)被釋放了,所以這個(gè)引用就是懸垂引用了。 解決這個(gè)的方法是返回一個(gè) String
而不是一個(gè)引用,這樣 s
就不會(huì)被釋放,而是把 s
的所有權(quán)轉(zhuǎn)移給了調(diào)用者,也就不存在懸垂引用了。
fn dangle() -> String { let s = String::from("hello"); s }
以上就是Rust 所有權(quán)機(jī)制原理深入剖析的詳細(xì)內(nèi)容,更多關(guān)于Rust 所有權(quán)機(jī)制的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Rust 標(biāo)準(zhǔn)庫的結(jié)構(gòu)及模塊路徑詳解
在 Rust 中,標(biāo)準(zhǔn)庫提供了一組核心功能,以幫助開發(fā)者執(zhí)行常見的編程任務(wù),這個(gè)路徑樹可以作為參考,幫助你更好地理解 Rust 標(biāo)準(zhǔn)庫的結(jié)構(gòu)和模塊之間的關(guān)系,本文介紹 Rust 標(biāo)準(zhǔn)庫的結(jié)構(gòu),并提供相應(yīng)的 use 路徑,感興趣的朋友一起看看吧2024-05-05Rust字符串字面值的一些經(jīng)驗(yàn)總結(jié)
字符串有兩種表現(xiàn)形式,一種是基本類型,表示字符串的切片,以&str表示,另一種是可變的string類型,下面這篇文章主要給大家介紹了關(guān)于Rust字符串字面值的相關(guān)資料,需要的朋友可以參考下2022-04-04在win10上使用mingw64編譯器配置Rust開發(fā)環(huán)境和idea 配置Rust 插件
在win10上配置 Rust 開發(fā)環(huán)境(使用 mingw64編譯器)和 idea 配置 Rust 插件的相關(guān)知識(shí),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2023-03-03Rust語言開發(fā)環(huán)境搭建詳細(xì)教程(圖文教程)
本文主要介紹了rust編程語言在windows上開發(fā)環(huán)境的搭建方法,文中通過圖文的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-02-02