認(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)在 x
和y
都等于 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)存又厉。這里s1
和s2
都指向同一堆內(nèi)存地址。當(dāng)s1
和s2
離開作用域時(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
,它的值是true
和false
- 所有的浮點(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è)功能叫做引用