Clone VS Copy
Rust 中有兩個常見的 trait,Clone 和 Copy,許多初學者沒有搞明白。今天我們來專門談一談這兩個 trait。
Copy 的含義
Copy 的全名是 std::marker::Copy。請大家注意 std::marker 這個模塊裡面的所有的 trait 都是特殊的 trait。目前穩定的有四個,它們是 Copy、Send、Sized、Sync。它們的特殊之處在於它們是跟編譯器密切綁定的,impl 這些 trait 對編譯器的行為有重要影響。在編譯器眼裡,它們與其它的 trait 不一樣。這幾個 trait 內部都沒有方法,它們的唯一任務是,給類型打一個「標記」,表明它符合某種約定,這些約定會影響編譯器的靜態檢查以及代碼生成。
Copy 這個 trait 在編譯器的眼裡代表的是什麼意思呢?簡單點總結就是說,如果一個類型 impl 了 Copy trait,意味著任何時候,我們可以通過簡單的內存拷貝(C語言的按位拷貝memcpy)實現該類型的複製,而不會產生任何問題。
一旦一個類型實現了 Copy trait,那麼它在變數綁定、函數參數傳遞、函數返回值傳遞等場景下,它都是 copy 語義,而不再是默認的 move 語義。
Copy 的實現條件
並不是所有的類型都可以實現Copy trait。Rust規定,對於自定義類型,只有所有的成員都實現了 Copy trait,這個類型才有資格實現 Copy trait。
常見的數字類型、bool類型、共享借用指針&,都是具有 Copy 屬性的類型。而 Box、Vec、可寫借用指針&mut 等類型都是不具備 Copy 屬性的類型。
對於數組類型,如果它內部的元素類型是Copy,那麼這個數組也是Copy類型。
對於tuple類型,如果它的每一個元素都是Copy類型,那麼這個tuple會自動實現Copy trait。
對於struct和enum類型,不會自動實現Copy trait。而且只有當struct和enum內部每個元素都是Copy類型的時候,編譯器才允許我們針對此類型實現Copy trait。
我們可以認為,Rust中只有 POD(C++語言中的Plain Old Data) 類型才有資格實現Copy trait。在Rust中,如果一個類型只包含POD數據類型的成員,沒有指針類型的成員,並且沒有自定義析構函數(實現Drop trait),那它就是POD類型。比如整數、浮點數、只包含POD類型的數組等,都屬於POD類型。而Box、 String、 Vec等,不能按 bit 位拷貝的類型,都不屬於POD類型。但是,反過來講,並不是所有的POD類型都應該實現Copy trait。
Clone 的含義
Clone 的全名是 std::clone::Clone。它的完整聲明是這樣的:
pub trait Clone : Sized { fn clone(&self) -> Self; fn clone_from(&mut self, source: &Self) { *self = source.clone() }}
它有兩個關聯方法,其中 clone_from 是有默認實現的,它依賴於 clone 方法的實現。clone 方法沒有默認實現,需要我們手動實現。
clone 方法一般用於「基於語義的複製」操作。所以,它做什麼事情,跟具體類型的作用息息相關。比如對於 Box 類型,clone 就是執行的「深拷貝」,而對於 Rc 類型,clone 做的事情就是把引用計數值加1。
雖然說,Rust中 clone 方法一般是用來執行複製操作的,但是你如果在自定義的 clone 函數中做點什麼別的工作編譯器也沒法禁止,你可以根據情況在 clone 函數中編寫任意的邏輯。但是有一條規則需要注意:對於實現了 Copy 的類型,它的 clone 方法應該跟 Copy 語義相容,等同於按位拷貝。
自動 derive
絕大多數情況下,實現 Copy Clone 這樣的 trait 都是一個重複而無聊的工作。因此,Rust提供了一個 attribute,讓我們可以利用編譯器自動生成這部分代碼。示例如下:
#[derive(Copy, Clone)]struct MyStruct(i32);
這裡的 derive 會讓編譯器幫我們自動生成 impl Copy 和 impl Clone 這樣的代碼。自動生成的 clone 方法,就是依次調用每個成員的 clone 方法。
通過 derive 方式自動實現 Copy 和手工實現 Copy 有一丁點的微小區別。當類型具有泛型參數的時候,比如 struct MyStruct<T>{},通過 derive 自動生成的代碼會自動添加一個 T: Copy 的約束。
目前,只有一部分固定的特殊 trait 可以通過 derive 來自動實現。將來 Rust 會允許自定義的 derive 行為,讓我們自己的 trait 也可以通過 derive 的方式自動實現。
總結
Copy 和 Clone 兩者的區別和聯繫有:
- Copy內部沒有方法,Clone內部有兩個方法。
- Copy trait 是給編譯器用的,告訴編譯器這個類型默認採用 copy 語義,而不是 move 語義。Clone trait 是給程序員用的,我們必須手動調用clone方法,它才能發揮作用。
- Copy trait不是你想實現就實現,它對類型是有要求的,有些類型就不可能 impl Copy。Clone trait 沒有什麼前提條件,任何類型都可以實現(unsized 類型除外)。
- Copy trait規定了這個類型在執行變數綁定、函數參數傳遞、函數返回等場景下的操作方式。即這個類型在這種場景下,必然執行的是「簡單內存拷貝」操作,這是由編譯器保證的,程序員無法控制。Clone trait 裡面的 clone 方法究竟會執行什麼操作,則是取決於程序員自己寫的邏輯。一般情況下,clone 方法應該執行一個「深拷貝」操作,但這不是強制的,如果你願意,也可以在裡面啟動一個人工智慧程序,都是有可能的。
- 如果你確實需要Clone trait執行「深拷貝」操作,編譯器幫我們提供了一個工具,我們可以在一個類型上添加#[derive(Clone)],來讓編譯器幫我們自動生成那些重複的代碼。
- 然而Rust語言規定了當T: Copy的情況下,Clone trait代表的含義。即:當某變數let t: T;,符合T: Copy時, 它調用 let x = t.clone() 方法的時候,它的含義必須等同於「簡單內存拷貝」。也就是說,clone的行為必須等同於let x = std::ptr::read(&t);,也等同於let x = t;。當T: Copy時,我們不要在Clone trait裡面亂寫自己的邏輯。所以,當我們需要指定一個類型是 Copy 的時候,最好順便也指定它 Clone 的行為,就是編譯器為我們自動生成的那個邏輯。正因為如此,在希望讓一個類型具有 Copy 性質的時候,一般使用 #[derive(Copy, Clone)] 這種方式,這種情況下它們倆最好一起出現,避免手工實現 Clone 導致錯誤。
推薦閱讀: