Przedmowa : Ta odpowiedź została napisana przed opt-in wbudowaną cech -specifically te Copy
aspekty -were realizowanych. Użyłem cudzysłowów blokowych, aby wskazać sekcje, które dotyczyły tylko starego schematu (tego, który był stosowany, gdy zadawano pytanie).
Stare : Aby odpowiedzieć na podstawowe pytanie, możesz dodać pole znacznika przechowujące NoCopy
wartość . Na przykład
struct Triplet {
one: int,
two: int,
three: int,
_marker: NoCopy
}
Możesz to również zrobić, mając destruktor (poprzez implementację Drop
cechy ), ale używanie typów znaczników jest preferowane, jeśli destruktor nic nie robi.
Typy są teraz przenoszone domyślnie, to znaczy, gdy definiujesz nowy typ, nie jest on implementowany, Copy
chyba że jawnie zaimplementujesz go dla swojego typu:
struct Triplet {
one: i32,
two: i32,
three: i32
}
impl Copy for Triplet {} // add this for copy, leave it out for move
Implementacja może istnieć tylko wtedy, gdy każdy typ zawarty w new struct
lub enum
jest sobą Copy
. Jeśli nie, kompilator wydrukuje komunikat o błędzie. Może również istnieć tylko wtedy, gdy typ nie ma Drop
implementacji.
Aby odpowiedzieć na pytanie, którego nie zadałeś ... „o co chodzi z przenoszeniem i kopiowaniem?”:
Najpierw zdefiniuję dwie różne „kopie”:
- kopia bajt , który jest po prostu płytko kopiując bajt bajt po obiekcie, nie następujące wskaźniki, na przykład, jeśli masz
(&usize, u64)
, to 16 bajty na komputerze 64-bitowym, a płytkie kopia będzie przy tych 16 bajtów i replikowania swoich wartość w innym 16-bajtowym fragmencie pamięci, bez dotykania usize
drugiego końca pliku &
. Oznacza to, że jest to równoważne z dzwonieniem memcpy
.
- semantyczny kopiowania , powielania wartość, aby utworzyć nową (nieco) niezależnej instancji, które mogą być bezpiecznie używane oddzielnie do starego. Np. Semantyczna kopia a
Rc<T>
wymaga po prostu zwiększenia liczby odniesień, a semantyczna kopia a Vec<T>
polega na utworzeniu nowej alokacji, a następnie semantycznym kopiowaniu każdego przechowywanego elementu ze starego do nowego. Mogą to być kopie głębokie (np. Vec<T>
) Lub płytkie (np. Rc<T>
Nie dotykają przechowywanych T
), Clone
są luźno definiowane jako najmniejsza ilość pracy wymagana do semantycznego skopiowania wartości typu T
z wnętrza a &T
do T
.
Rust jest podobny do C, każde użycie wartości według wartości jest kopią bajtową:
let x: T = ...;
let y: T = x; // byte copy
fn foo(z: T) -> T {
return z // byte copy
}
foo(y) // byte copy
Są to kopie bajtowe, niezależnie od tego, czy są T
przenoszone, czy też są „niejawnie kopiowalne”. (Aby było jasne, nie zawsze są to kopie bajt po bajcie w czasie wykonywania: kompilator może optymalizować kopie, jeśli zachowanie kodu jest zachowane.)
Istnieje jednak podstawowy problem z kopiami bajtowymi: kończy się to ze zduplikowanymi wartościami w pamięci, co może być bardzo złe, jeśli mają destruktory, np.
{
let v: Vec<u8> = vec![1, 2, 3];
let w: Vec<u8> = v;
} // destructors run here
Gdyby w
była to zwykła kopia bajtowa, v
byłyby dwa wektory wskazujące na tę samą alokację, oba z destruktorami, które ją zwalniają ... powodując podwójne zwolnienie , co jest problemem. NB. Byłoby to całkowicie w porządku, gdybyśmy zrobili semantyczną kopię programu v
into w
, ponieważ wtedy w
byłyby jego własne niezależne, Vec<u8>
a destruktory nie deptałyby się nawzajem.
Istnieje kilka możliwych poprawek:
- Pozwól programiście zająć się tym, jak C. (w C nie ma destruktorów, więc nie jest tak źle ... zamiast tego zostajesz z wyciekami pamięci.: P)
- Wykonaj kopię semantyczną niejawnie, tak aby
w
miała własną alokację, podobnie jak C ++ z konstruktorami kopiowania.
- Traktuj wykorzystanie według wartości jako przeniesienie własności, więc
v
nie można go już używać i nie można uruchomić jego destruktora.
Ostatnim jest to, co robi Rust: ruch to tylko użycie wartości, w którym źródło jest statycznie unieważnione, więc kompilator zapobiega dalszemu używaniu niepoprawnej pamięci.
let v: Vec<u8> = vec![1, 2, 3];
let w: Vec<u8> = v;
println!("{}", v); // error: use of moved value
Typy, które mają destruktory, muszą się przesuwać, gdy są używane przez wartość (inaczej podczas kopiowania bajtu), ponieważ mają zarządzanie / własność jakiegoś zasobu (np. Alokację pamięci lub uchwyt pliku) i jest bardzo mało prawdopodobne, aby kopia bajtowa poprawnie to powieliła własność.
„Cóż… co to jest ukryta kopia?”
Pomyśl o typie pierwotnym, takim jak u8
: kopia bajtowa jest prosta, po prostu skopiuj pojedynczy bajt, a kopia semantyczna jest równie prosta, skopiuj pojedynczy bajt. W szczególności kopia bajtowa jest kopią semantyczną ... Rust ma nawet wbudowaną cechę,Copy
która przechwytuje, które typy mają identyczne kopie semantyczne i bajtowe.
W związku z tym dla tych Copy
typów zastosowań według wartości są również automatycznie kopiami semantycznymi, więc dalsze korzystanie ze źródła jest całkowicie bezpieczne.
let v: u8 = 1;
let w: u8 = v;
println!("{}", v); // perfectly fine
Stary : NoCopy
Znacznik przesłania automatyczne zachowanie kompilatora polegające na założeniu, że typy, które mogą być Copy
(tj. Zawierają tylko agregaty prymitywów i &
), są Copy
. Jednak ulegnie to zmianie, gdy zaimplementowane zostaną wbudowane cechy optyczne .
Jak wspomniano powyżej, wbudowane cechy opt-in są zaimplementowane, więc kompilator nie ma już automatycznego zachowania. Jednak reguła stosowana w przeszłości do automatycznego zachowania to te same reguły, które służą do sprawdzania, czy ich wdrożenie jest legalne Copy
.