引用類型的Copy和Clone
引用類型是可Copy的,所以引用類型在Move的時候都會Copy一個引用的副本,Copy前後的引用都指向同一個目標值,這很容易理解。
#![allow(unused)] fn main() { let a = "hello world".to_string(); // b和c都是a的引用 let b = &a; let c = b; // Copy引用 }
引用類型也是可Clone的(實現Copy的時候要求也必須實現Clone,所以可Copy的類型也是可Clone的),但是引用類型的clone()需注意。
例如:
#![allow(unused)] fn main() { struct Person; let a = Person; let b = &a; let c = b.clone(); // c的類型是&Person }
如果使用了clippy工具檢查代碼,clippy將對上面的b.clone()
給出錯誤提示:
using `clone` on a double-reference; this will copy the reference of type `&strategy::Strategy::run::Person` instead of cloning the inner type
提示說明,對引用clone()時,將拷貝引用類型本身,而不是去拷貝引用所指向的數據本身,所以變量c的類型是&Person
。這裡引用的clone()邏輯,看上去似乎沒有問題,但是卻發出了錯誤提示。
但如果,在引用所指向的類型上去實現Clone,再去clone()引用類型,將沒有錯誤提示。
#![allow(unused)] fn main() { #[derive(Clone)] struct Person; let a = Person; let b = &a; let c = b.clone(); // c的類型是Person,而不是&Person }
注意上面b.clone()
得到的類型是引用所指向數據的類型,即Person
,而不是像之前示例中的那樣得到&Person
。
前後兩個示例的區別,僅在於引用所指向的類型Person
有沒有實現Clone。所以得到結論:
- 沒有實現Clone時,引用類型的clone()將等價於Copy,但cilppy工具的錯誤提示說明這很可能不是我們想要的克隆效果
- 實現了Clone時,引用類型的clone()將克隆並得到引用所指向的類型
同一種類型的同一個方法,調用時卻產生兩種效果,之所以會有這樣的區別,是因為:
- 方法調用的符號
.
會自動解引用 - 方法調用前會先查找方法,查找方法時有優先級,找得到即停。由於解引用的前和後是兩種類型(解引用前是引用類型,解引用後是引用指向的類型),如果這兩種類型都實現了同一個方法(比如
clone()
),Rust編譯器將按照方法查找規則來決定調用哪個類型上的方法,參考(https://rustc-dev-guide.rust-lang.org/method-lookup.html?highlight=lookup#method-lookup)
為什麼clone引用的時候,clippy工具會提示這很可能不是我們想要的行為呢?一方面,拷貝一個引用得到另一個引用副本是很常見的需求,但是這個需求有Copy就夠了,另一方面,正如clippy所提示的,能夠拷貝引用背後的數據也是非常有必要的。
例如,某方法要求返回Person類型,但在該方法內部卻只能取得Person的引用類型(比如從HashMap的get()
方法只能返回值的引用),所以需要將引用&Person
轉換為Person
,直接解引用是一種可行方案,但是對未實現Copy的類型去解引用,將會執行Move操作,很多時候這是不允許的,比如不允許將已經存入HashMap中的值Move出來,此時最簡單的方式,就是通過克隆引用的方式得到Person類型。
提醒:正因為從集合(比如HashMap、BTreeMap等)中取數據後很有可能需要對取得的數據進行克隆,因此建議不要將大體量的數據存入集合,如果確實需要克隆集合中的數據的話,這將會嚴重影響性能。
作為建議,可以考慮先將大體量的數據封裝在智能指針(比如Box、Rc等)的背後,再將智能指針存入集合。
其它語言中集合類型的使用可能非常簡單直接,但Rust中需要去關注這一點。