Kata Pengantar : Jawaban ini ditulis sebelum opt-in built-in sifat -Khusus yang Copy
aspek -were dilaksanakan. Saya telah menggunakan tanda kutip blok untuk menunjukkan bagian yang hanya diterapkan pada skema lama (yang diterapkan saat pertanyaan diajukan).
Lama : Untuk menjawab pertanyaan dasar, Anda dapat menambahkan bidang penanda yang menyimpan NoCopy
nilai . Misalnya
struct Triplet {
one: int,
two: int,
three: int,
_marker: NoCopy
}
Anda juga dapat melakukannya dengan memiliki destruktor (melalui penerapan Drop
sifat ), tetapi menggunakan jenis penanda lebih disukai jika destruktor tidak melakukan apa pun.
Tipe sekarang berpindah secara default, yaitu, ketika Anda mendefinisikan tipe baru yang tidak diimplementasikan Copy
kecuali Anda secara eksplisit mengimplementasikannya untuk tipe Anda:
struct Triplet {
one: i32,
two: i32,
three: i32
}
impl Copy for Triplet {} // add this for copy, leave it out for move
Implementasi hanya bisa ada jika setiap tipe terdapat dalam new struct
atau enum
itu sendiri Copy
. Jika tidak, kompilator akan mencetak pesan kesalahan. Itu juga hanya bisa ada jika tipe tidak memiliki Drop
implementasi.
Untuk menjawab pertanyaan yang tidak Anda tanyakan ... "ada apa dengan gerakan dan salinan?":
Pertama saya akan menentukan dua "salinan" yang berbeda:
- sebuah salinan byte , yang hanya dangkal menyalin obyek byte-by-byte, pointer tidak mengikuti, misalnya jika Anda memiliki
(&usize, u64)
, itu adalah 16 byte pada komputer 64-bit, dan salinan dangkal akan mengambil orang-orang 16 byte dan mereplikasi mereka nilai di beberapa bagian memori 16-byte lainnya, tanpa menyentuh usize
di ujung lain dari &
. Artinya, itu setara dengan menelepon memcpy
.
- a copy semantik , menduplikasi nilai untuk membuat (agak) misalnya independen baru yang dapat digunakan dengan aman secara terpisah dengan yang lama. Misalnya, salinan semantik dari suatu
Rc<T>
hanya melibatkan peningkatan jumlah referensi, dan salinan semantik a Vec<T>
melibatkan pembuatan alokasi baru, dan kemudian secara semantik menyalin setiap elemen yang disimpan dari yang lama ke yang baru. Ini dapat berupa salinan dalam (mis. Vec<T>
) Atau dangkal (mis. Rc<T>
Tidak menyentuh yang disimpan T
), Clone
secara longgar didefinisikan sebagai jumlah pekerjaan terkecil yang diperlukan untuk menyalin secara semantik nilai tipe T
dari dalam a &T
ke T
.
Rust seperti C, setiap nilai yang digunakan dari suatu nilai adalah salinan byte:
let x: T = ...;
let y: T = x; // byte copy
fn foo(z: T) -> T {
return z // byte copy
}
foo(y) // byte copy
Mereka adalah salinan byte apakah T
bergerak atau tidak atau "dapat disalin secara implisit". (Untuk lebih jelasnya, mereka tidak harus benar-benar salinan byte-by-byte pada saat run-time: kompilator bebas untuk mengoptimalkan salinan jika perilaku kode dipertahankan.)
Namun, ada masalah mendasar dengan salinan byte: Anda berakhir dengan nilai duplikat di memori, yang bisa sangat buruk jika memiliki destruktor, mis.
{
let v: Vec<u8> = vec![1, 2, 3];
let w: Vec<u8> = v;
} // destructors run here
Jika w
hanya salinan byte biasa v
maka akan ada dua vektor menunjuk pada alokasi yang sama, keduanya dengan penghancur yang membebaskannya ... menyebabkan bebas ganda , yang merupakan masalah. NB. Ini akan baik-baik saja, jika kita melakukan salinan semantik v
ke w
, karena itu w
akan menjadi independennya sendiri Vec<u8>
dan penghancurnya tidak akan menginjak-injak satu sama lain.
Ada beberapa kemungkinan perbaikan di sini:
- Biarkan programmer menanganinya, seperti C. (tidak ada destruktor di C, jadi tidak seburuk ... Anda hanya mendapatkan kebocoran memori saja.: P)
- Lakukan salinan semantik secara implisit, sehingga
w
memiliki alokasinya sendiri, seperti C ++ dengan konstruktor salinannya.
- Menganggap penggunaan nilai sebagai pengalihan kepemilikan, sehingga
v
tidak dapat digunakan lagi dan destruktornya tidak berjalan.
Yang terakhir adalah apa yang Rust lakukan: perpindahan hanyalah penggunaan nilai di mana sumber secara statis tidak valid, sehingga kompilator mencegah penggunaan lebih lanjut dari memori yang sekarang tidak valid.
let v: Vec<u8> = vec![1, 2, 3];
let w: Vec<u8> = v;
println!("{}", v); // error: use of moved value
Jenis yang memiliki destruktor harus dipindahkan ketika digunakan oleh nilai (alias ketika byte disalin), karena mereka memiliki manajemen / kepemilikan beberapa sumber daya (misalnya alokasi memori, atau pegangan file) dan sangat tidak mungkin salinan byte akan menduplikasi ini dengan benar kepemilikan.
"Nah ... apa itu salinan implisit?"
Pikirkan tentang tipe primitif seperti u8
: salinan byte sederhana, cukup salin satu byte, dan salinan semantik sama sederhananya, salin satu byte. Secara khusus, salinan byte adalah salinan semantik ... Rust bahkan memiliki sifat bawaanCopy
yang menangkap tipe mana yang memiliki salinan semantik dan byte yang identik.
Karenanya, untuk Copy
tipe ini penggunaan nilai menurut juga otomatis merupakan salinan semantik, sehingga sangat aman untuk terus menggunakan sumbernya.
let v: u8 = 1;
let w: u8 = v;
println!("{}", v); // perfectly fine
Lama : NoCopy
Marker menimpa perilaku otomatis penyusun dengan mengasumsikan bahwa tipe yang bisa Copy
(yaitu hanya berisi agregat primitif dan &
) adalah Copy
. Namun ini akan berubah ketika sifat bawaan keikutsertaan diterapkan.
Seperti disebutkan di atas, sifat bawaan keikutsertaan diimplementasikan, sehingga kompilator tidak lagi memiliki perilaku otomatis. Namun, aturan yang digunakan untuk perilaku otomatis di masa lalu adalah aturan yang sama untuk memeriksa apakah legal untuk diterapkan Copy
.