引用類型的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中需要去關注這一點。