Vorwort : Diese Antwort wurde geschrieben , bevor Opt-in - built-in Zügen -specifically die CopyAspekte umgesetzt -were. Ich habe Blockzitate verwendet, um die Abschnitte anzugeben, die nur für das alte Schema galten (den, der galt, als die Frage gestellt wurde).
Alt : Um die grundlegende Frage zu beantworten, können Sie ein Markierungsfeld hinzufügen, in dem ein NoCopyWert gespeichert ist . Z.B
struct Triplet {
one: int,
two: int,
three: int,
_marker: NoCopy
}
Sie können dies auch tun, indem Sie einen Destruktor haben (indem Sie das DropMerkmal implementieren ). Die Verwendung der Markertypen wird jedoch bevorzugt, wenn der Destruktor nichts tut.
Typen werden jetzt standardmäßig verschoben, dh wenn Sie einen neuen Typ definieren, wird dieser nur implementiert, Copywenn Sie ihn explizit für Ihren Typ implementieren:
struct Triplet {
one: i32,
two: i32,
three: i32
}
impl Copy for Triplet {} // add this for copy, leave it out for move
Die Implementierung kann nur existieren, wenn jeder Typ im neuen enthalten ist structoder enumselbst ist Copy. Wenn nicht, gibt der Compiler eine Fehlermeldung aus. Es kann auch nur existieren , wenn der Typ nicht funktioniert eine hat DropImplementierung.
Um die Frage zu beantworten, die Sie nicht gestellt haben ... "Was ist mit Zügen und Kopieren los?":
Zunächst definiere ich zwei verschiedene "Kopien":
- Eine Byte-Kopie , die ein Objekt nur flach byteweise kopiert und keinen Zeigern folgt, z. B. wenn
(&usize, u64)dies der Fall ist, sind es 16 Bytes auf einem 64-Bit-Computer, und eine flache Kopie würde diese 16 Bytes nehmen und ihre replizieren Wert in einem anderen 16-Byte-Speicherblock, ohne den usizeam anderen Ende des zu berühren &. Das heißt, es ist gleichbedeutend mit einem Anruf memcpy.
- Eine semantische Kopie , die einen Wert dupliziert, um eine neue (etwas) unabhängige Instanz zu erstellen, die sicher getrennt von der alten verwendet werden kann. Bei einer semantischen Kopie von a wird beispielsweise
Rc<T>nur die Referenzanzahl erhöht, und bei einer semantischen Kopie von a wird Vec<T>eine neue Zuordnung erstellt und anschließend jedes gespeicherte Element semantisch vom alten in das neue kopiert. Dies können tiefe Kopien (z. B. Vec<T>) oder flache Kopien sein (z. B. Rc<T>berührt das Gespeicherte nicht T). Dies Cloneist lose definiert als der kleinste Arbeitsaufwand, der erforderlich ist, um einen Wert vom Typ Tinnerhalb von a &Tnach semantisch zu kopieren T.
Rust ist wie C, jede Verwendung eines Werts nach Wert ist eine Bytekopie:
let x: T = ...;
let y: T = x; // byte copy
fn foo(z: T) -> T {
return z // byte copy
}
foo(y) // byte copy
Sie sind Byte-Kopien, unabhängig davon, ob sie Tverschoben werden oder "implizit kopierbar" sind. (Um klar zu sein, es handelt sich nicht unbedingt um buchstäblich byteweise Kopien zur Laufzeit: Der Compiler kann die Kopien optimieren, wenn das Verhalten des Codes erhalten bleibt.)
Bei Byte-Kopien gibt es jedoch ein grundlegendes Problem: Sie haben doppelte Werte im Speicher, die sehr schlecht sein können, wenn sie Destruktoren haben, z
{
let v: Vec<u8> = vec![1, 2, 3];
let w: Vec<u8> = v;
} // destructors run here
Wenn wnur eine einfache Bytekopie von vwäre, würden zwei Vektoren auf dieselbe Zuordnung zeigen, beide mit Destruktoren, die sie freigeben ... was eine doppelte Freigabe verursacht , was ein Problem ist. NB. Dies wäre vollkommen in Ordnung, wenn wir eine semantische Kopie von vin machen würden w, da dies dann wseine eigene Unabhängigkeit wäre Vec<u8>und die Destruktoren sich nicht gegenseitig mit Füßen treten würden .
Hier gibt es einige mögliche Korrekturen:
- Lassen Sie den Programmierer damit umgehen, wie C. (es gibt keine Destruktoren in C, also ist es nicht so schlimm ... Sie haben stattdessen nur Speicherlecks .: P)
- Führen Sie implizit eine semantische Kopie durch, sodass diese
weine eigene Zuordnung hat, wie z. B. C ++ mit ihren Kopierkonstruktoren.
- Betrachten Sie By-Value-Verwendungen als Eigentumsübertragung, damit diese
vnicht mehr verwendet werden können und der Destruktor nicht ausgeführt wird.
Das Letzte ist, was Rust tut: Eine Verschiebung ist nur eine Verwendung nach Wert, bei der die Quelle statisch ungültig ist, sodass der Compiler die weitere Verwendung des jetzt ungültigen Speichers verhindert.
let v: Vec<u8> = vec![1, 2, 3];
let w: Vec<u8> = v;
println!("{}", v); // error: use of moved value
Typen mit Destruktoren müssen verschoben werden, wenn sie als Wert verwendet werden (auch bekannt als beim Kopieren von Bytes), da sie eine Ressource verwalten / besitzen (z. B. eine Speicherzuweisung oder ein Dateihandle) und es sehr unwahrscheinlich ist, dass eine Bytekopie diese korrekt dupliziert Eigentum.
"Nun ... was ist eine implizite Kopie?"
Stellen Sie sich einen primitiven Typ vor wie u8: Eine Byte-Kopie ist einfach, kopieren Sie einfach das einzelne Byte, und eine semantische Kopie ist genauso einfach, kopieren Sie das einzelne Byte. Insbesondere wird ein Byte - Kopie ist eine semantische Kopie ... Rust hat sogar ein Einbau-MerkmalCopy , dass Aufnahmen , die Typen identisch semantische und Byte - Kopien haben.
Daher sind CopyBy-Value-Verwendungen für diese Typen auch automatisch semantische Kopien, sodass die Verwendung der Quelle absolut sicher ist.
let v: u8 = 1;
let w: u8 = v;
println!("{}", v); // perfectly fine
Alt : Der NoCopyMarker überschreibt das automatische Verhalten des Compilers bei der Annahme, dass Typen sein können Copy(dh nur Aggregate von Grundelementen und enthalten &) Copy. Dies wird sich jedoch ändern, wenn integrierte Opt-In-Merkmale implementiert werden.
Wie oben erwähnt, sind integrierte Opt-In-Merkmale implementiert, sodass der Compiler kein automatisches Verhalten mehr aufweist. Die in der Vergangenheit für das automatische Verhalten verwendete Regel entspricht jedoch den Regeln für die Überprüfung, ob die Implementierung zulässig ist Copy.