帶你了解 Rust 中的 move, copy, clone
move, copy, clone
?
原文:https://hashrust.com/blog/moves-copies-and-clones-in-rust/
譯者:韓玄亮(一個(gè)熱愛開源,喜歡?rust?的?go?開發(fā)者)
本文對?move, copy, clone?不做中文翻譯,保持在?Rust?中的味道,翻譯了就沒哪味。
?
介紹
?
移動(dòng)和復(fù)制是?Rust?中的基本概念。對于來自?Ruby、Python?或?C#?等具有垃圾回收功能語言的開發(fā)者來說,這些概念可能是完全陌生的。雖然這些術(shù)語在?C++?中是存在的,但它們在?Rust?中的含義略有不同。在這篇文章中,我將解釋在?Rust?中?move、copy?和?clone?的含義。就讓我們一探究竟吧。
?
Move
正如「Rust?中的內(nèi)存安全?- 2」所說的,將一個(gè)變量賦值給另一個(gè)變量會(huì)將所有權(quán)轉(zhuǎn)移。
在上述例子中,v?被移到了?v1?上。但移動(dòng)?v?是什么意思?為了理解這一點(diǎn),我們需要看看?Vec?在內(nèi)存中的布局結(jié)構(gòu):

Vec?內(nèi)部維護(hù)一個(gè)動(dòng)態(tài)增長或收縮的緩沖區(qū)。這個(gè)緩沖區(qū)是在堆上分配的,包含?Vec?的實(shí)際元素。此外,Vec?在棧上還有一個(gè)小對象。這個(gè)對象包含一些管理信息:一個(gè)指向堆上緩沖區(qū)的指針,緩沖區(qū)的容量和長度(即當(dāng)前有多少部分已經(jīng)被填滿)。
?
當(dāng)變量?v?被移動(dòng)到?v1?時(shí),棧上的對象按位復(fù)制?(stack copy):

???:?在上面的例子中,實(shí)際上發(fā)生的是一個(gè)淺拷貝(也就是按位復(fù)制)。這與?C++?截然然不同,C++?在執(zhí)行一個(gè)?vector?賦值給另一個(gè)變量時(shí)會(huì)進(jìn)行深拷貝。堆上的緩沖區(qū)保持不變。但這里發(fā)生了一次移動(dòng):現(xiàn)在是?v1?負(fù)責(zé)釋放堆上緩沖區(qū),而不是?v:
?
這種所有權(quán)的改變是有益的,因?yàn)槿绻瑫r(shí)允許通過?v?和?v1?訪問緩沖區(qū)數(shù)據(jù),那么你將會(huì)得到兩個(gè)棧對象指向同一個(gè)堆緩沖區(qū):

在這種情況下,哪個(gè)對象有權(quán)釋放緩沖區(qū)?這點(diǎn)并不清楚,而?Rust?從根本就防止了這種情況的出現(xiàn)。(即:賦值?→?棧對象拷貝,同時(shí)轉(zhuǎn)移所有權(quán),保證一個(gè)對象在同一時(shí)間只能有一個(gè)所有者)
?
當(dāng)然,賦值并不是唯一涉及移動(dòng)的操作。值在作為參數(shù)傳遞或從函數(shù)返回時(shí)也會(huì)被移動(dòng):
?或是賦給結(jié)構(gòu)體或?enum?的成員:?
這都是關(guān)于?move?的內(nèi)容。接下來讓我們看看?copy。
?
Copy
還記得上面的例子嗎?
如果我們把變量?v?和?v1?的類型從?Vec?改為?i32,會(huì)發(fā)生什么呢?
這幾乎是相同的代碼。為什么賦值操作這次不把?v?移到?v1?中呢?為了了解這一點(diǎn),讓我們再次看看堆棧中的內(nèi)存布局:

在本例中,值是完全只存儲(chǔ)在棧中。堆上沒有東西可以擁有。這就是為什么允許通過?v?和?v1?訪問是可以的?——?因?yàn)樗鼈兪峭耆?dú)立的拷貝。
?
這種不擁有其他資源并且可以按位復(fù)制的類型稱為復(fù)制類型。它們實(shí)現(xiàn)了?Copy Trait[1]。目前所有基本類型,如整數(shù)、浮點(diǎn)數(shù)和字符都是?Copy?類型。默認(rèn)情況下,struct/enum?不是Copy,但你可以派生?Copy trait:
???:?需要在?#[derive()]?中同時(shí)使用?Clone,因?yàn)?Copy?是這樣定義的: pub trait Copy: Clone {}
?
但是要使?#[derive(Copy, Clone)]?起作用,struct?或?enum?的所有成員必須可以?Copy。例如,下面代碼就不起作用:
當(dāng)然,你也可以手動(dòng)實(shí)現(xiàn)?Copy?和?Clone:
???: marker trait →?本身沒有任何行為,但被用于給編譯器提供某些保證。具體可以看這里[2]
?
但是一般來說,任何實(shí)現(xiàn)?Drop?的類型都不能被?Copy,因?yàn)?Drop?是由擁有一些資源的類型實(shí)現(xiàn)的。因?yàn)椴荒鼙缓唵蔚匕次粡?fù)制,但是?Copy?類型應(yīng)該是可以被簡單復(fù)制的。因此,?Drop?和?Copy?不能很好地混合。
?
這就是關(guān)于?Copy?的全部內(nèi)容。接下來是?Clone。
?
Clone
?
當(dāng)一個(gè)值被移動(dòng)時(shí),Rust?會(huì)做一個(gè)淺拷貝;但是如果你想創(chuàng)建一個(gè)像?C++那樣的深拷貝呢?
為了實(shí)現(xiàn)這一點(diǎn),一個(gè)類型必須首先得實(shí)現(xiàn)?Clone Trait[3]。然后,為了能進(jìn)行深復(fù)制,調(diào)用端代碼應(yīng)該執(zhí)行?clone():
clone()?調(diào)用后,內(nèi)存布局如下:

?
由于深拷貝,v?和?v1?都可以自由獨(dú)立地釋放它們對應(yīng)的堆緩沖區(qū)數(shù)據(jù)。
?
???: clone()?并不總是創(chuàng)建深拷貝。類型可以自由地以任何他們想要的方式實(shí)現(xiàn)?clone(),但在語義上它應(yīng)該足夠接近復(fù)制一個(gè)對象的含義。例如,Rc/Arc?會(huì)增加引用計(jì)數(shù)。
?
這就是關(guān)于?Clone?的所有內(nèi)容。
?
總結(jié)
?
在這篇文章中,我深入分析了?Rust?中的?Move/Copy/Clone?的語義。同時(shí)在文章中試圖捕捉與?C++?中,在語義上的細(xì)微差別。
Rust?之所以優(yōu)秀,是因?yàn)樗写罅康哪J(rèn)行為。例如,Rust?中的賦值操作符要么移動(dòng)值(轉(zhuǎn)移所有權(quán)),要么進(jìn)行簡單的按位復(fù)制(淺拷貝)。
?
另一方面,在?C++?中,看似無害的賦值操作可以隱藏大量的代碼,而這些代碼作為重載賦值運(yùn)算符的一部分運(yùn)行。在?Rust?中,這樣的代碼是公開的,因?yàn)槌绦騿T必須顯式地調(diào)用clone()。
?
有人可能會(huì)說,這兩種語言做出了不同的權(quán)衡,但我喜歡?Rust?由于這些設(shè)計(jì)權(quán)衡而帶來的額外安全保障。
?
References
[1] Copy Trait: https://doc.rust-lang.org/std/marker/trait.Copy.html
[2]?這里: https://doc.rust-lang.org/std/marker/index.html
[3] Clone Trait: https://doc.rust-lang.org/std/clone/trait.Clone.html
