Move ist nicht immer das, was du willst. Manchmal brauchst du zwei unabhängige Werte statt einem, der hin und herwandert. Rust bietet dafür zwei Mechanismen: den Copy-Trait als implizite Bit-Kopie bei Zuweisung (für triviale Typen wie i32) und den Clone-Trait als explizit aufgerufene Methode für tiefe Kopien (für komplexere Typen wie String oder Vec). Beide Traits sehen ähnlich aus, sind aber semantisch sehr unterschiedlich. Dieser Artikel zeigt, wann welcher Trait passt, welche Stdlib-Typen ihn implementieren, und welche Konsequenzen das Derive auf eigene Typen hat.
Copy — die implizite Bit-Kopie
Copy ist ein Marker-Trait ohne Methoden. Ein Typ, der Copy ist, wird bei Zuweisung kopiert statt gemoved:
fn main() {
let a: i32 = 5;
let b = a; // Bit-Kopie — keine Markierung von a als „leer"
println!("{a} {b}"); // beide nutzbar: 5 5
}Der Compiler kopiert die Bytes von a in den Stack-Slot von b. Danach gibt es zwei unabhängige Werte. Es passiert kein Drop für a separat — das wäre auch unnötig, weil i32 keinen Drop-Code hat (nichts auf dem Heap, kein File-Handle).
Welche Typen sind Copy?
Per Default Copy:
- Primitive:
i8,i16,i32,i64,i128,isize,u8,u16,u32,u64,u128,usize,f32,f64,bool,char. - Shared-Referenzen:
&Tfür jedesT(die Referenz selbst, nicht das Ziel). - Funktions-Pointer:
fn(...). - Raw-Pointer:
*const T,*mut T. - Tupel aus
Copy-Typen:(i32, f64, bool)istCopy. - Arrays aus
Copy-Typen:[u8; 32]istCopy.
Niemals Copy:
- Heap-allokierte Typen:
String,Vec<T>,Box<T>,HashMap,BTreeMap. - Mutable Referenzen:
&mut T(dürfen nur eine pro Wert haben — keine Kopie). - Typen mit eigenem
Drop-Impl. - Owned Smart Pointer:
Rc<T>,Arc<T>(haben Custom-Clone, sind aber nichtCopy).
Marker-Charakter
Copy hat keine Methoden — er signalisiert dem Compiler nur „dieser Typ darf bit-kopiert werden". Die Copy-Implementierung sieht so aus:
// Aus der Stdlib (vereinfacht):
pub trait Copy: Clone {}Zwei wichtige Eigenschaften:
- Trait ohne Methoden — pure Markierung.
Copy: Clone— jederCopy-Typ ist auch automatischClone. WerCopyderive-t, muss auchClonederive-en.
Clone — die explizite Kopie
Clone hat eine Methode, die explizit aufgerufen wird:
fn main() {
let s1 = String::from("Hi");
let s2 = s1.clone(); // tiefe Kopie — neuer Heap-Allokat
println!("{s1} {s2}"); // beide unabhängig nutzbar
}s1.clone() löst:
- Heap-Allokation für die String-Bytes.
- Bit-Kopie der Bytes vom alten Heap-Block in den neuen.
- Neuer Stack-Slot
s2mit Pointer auf den neuen Block.
Resultat: zwei String-Werte, beide besitzen unabhängige Heap-Blöcke mit identischem Inhalt. Beide werden beim Scope-Ende einzeln gedroppt.
Clone-Trait-Definition
// Aus der Stdlib (vereinfacht):
pub trait Clone: Sized {
fn clone(&self) -> Self;
// Default-Implementierung
fn clone_from(&mut self, source: &Self) {
*self = source.clone();
}
}Die zentrale Methode ist clone() -> Self. Jeder Clone-Typ kann von einer &self-Referenz aus eine neue Instanz produzieren.
clone_from ist eine Optimierung: wenn self schon Heap-Speicher hat, kann er manchmal wiederverwendet werden. Standardmäßig (Default-Impl) macht es einfach *self = source.clone(). Viele Container überschreiben das für bessere Performance.
Copy vs. Clone — die Unterschiede
| Aspekt | Copy | Clone |
|---|---|---|
| Aufruf | Implizit (Compiler) | Explizit (.clone()) |
| Kosten | Bit-Kopie (immer billig) | Beliebig teuer (Heap-Alloc möglich) |
| Wo erlaubt | Nur Stack-Daten ohne Drop | Beliebige Daten |
| Beziehung | Copy: Clone — Copy ist Subset | Clone ist breiter |
| Methoden | keine (Marker) | clone(), clone_from() |
| Drop-Kompatibilität | Typ darf keinen Drop haben | Typ darf Drop haben |
Die zentrale Faustregel: Copy für triviale Werte, Clone für alles, was Heap berührt oder eigene Cleanup-Logik braucht.
derive(Copy, Clone) auf eigene Typen
Für eigene Structs und Enums kannst du die beiden Traits per Macro generieren:
#[derive(Copy, Clone, Debug)]
struct Punkt {
x: f64,
y: f64,
}
fn main() {
let p1 = Punkt { x: 1.0, y: 2.0 };
let p2 = p1; // Bit-Kopie
println!("{p1:?} {p2:?}"); // beide nutzbar
}Voraussetzung für derive(Copy): alle Felder müssen selbst Copy sein. Bei Punkt { x: f64, y: f64 } trifft das zu — beide Felder sind Copy-Primitive.
Sobald ein Feld nicht Copy ist:
#[derive(Clone)] // OK
// #[derive(Copy, Clone)] // Compile-Fehler — String ist nicht Copy
struct Person {
name: String,
alter: u8,
}String ist nicht Copy, also kann Person auch nicht Copy sein. Clone alleine geht aber — und macht eine tiefe Kopie, inklusive Heap-Duplizierung des String.
Faustregeln für derive
| Typ-Charakteristik | Empfehlung |
|---|---|
| Klein, alle Felder primitive | #[derive(Copy, Clone)] |
| Mittel, mit String oder Vec | #[derive(Clone)] |
| Hält Rohe Ressourcen (File, Socket) | Kein Clone — oder bewusst manuell |
Newtype über kleine Copy-Typen | #[derive(Copy, Clone)] |
| Konfiguration mit vielen Strings | #[derive(Clone)], sparsam klonen |
Wann ist Copy eine schlechte Idee?
Copy ist nicht immer sinnvoll, auch wenn es technisch möglich wäre. Drei Fälle:
1. Versteckte Performance-Kosten
#[derive(Copy, Clone)]
struct GrosserBuffer {
daten: [u8; 4096], // 4 KB pro Wert!
}
fn nehmen(b: GrosserBuffer) { // Bei jedem Call: 4 KB kopiert
println!("{}", b.daten[0]);
}Technisch ist [u8; 4096] Copy, also kann der Struct Copy sein. Aber jeder Funktions-Call kopiert 4 KB im Stack — das ist nicht trivial. Besser: &GrosserBuffer als Parameter, oder den Struct nicht Copy machen.
2. Semantische Bedeutung von Identität
// Schlechte Idee — UUID sollte nicht beliebig kopierbar sein:
// #[derive(Copy, Clone)]
// struct UserId(u64);
// Besser — Clone, aber kein Copy:
#[derive(Clone)]
struct UserId(u64);Bei IDs, Tokens, Schlüsseln kann unbeabsichtigtes Kopieren zu logischen Bugs führen — etwa zwei Stellen, die „denselben" User haben, aber durch versehentlich kopierte IDs. Clone macht das Kopieren explizit und sichtbar.
3. Mutable Borrow notwendig
// #[derive(Copy, Clone)]
struct Zaehler { wert: u64 }
impl Zaehler {
fn neu() -> Self { Zaehler { wert: 0 } }
fn inc(&mut self) { self.wert += 1; }
}Wenn Zaehler Copy wäre, würde let z2 = z1; z2.inc(); den Zähler einer Kopie inkrementieren — z1 bliebe unverändert. Bei Aggregat-Typen mit Mutation ist Copy meist unsinnig.
Custom Clone — manuelle Implementierung
derive(Clone) macht für jedes Feld einen eigenen clone-Aufruf. Manchmal will man das anders:
struct Cache {
daten: Vec<u8>,
generation: u64,
}
impl Clone for Cache {
fn clone(&self) -> Self {
Cache {
daten: self.daten.clone(),
generation: self.generation + 1, // Klon zählt als „neue Generation"
}
}
}Manuelles Clone ist selten nötig, aber praktisch bei:
- Generation-/Version-Counter, die beim Klonen erhöht werden.
- Klone, die zusätzlich loggen.
- Klone, die statt Heap-Duplikat eine Reference Count erhöhen (siehe
Rc/Arc).
Clone bei Rc und Arc — semantische Unterschiede
Eine Besonderheit: bei Rc<T> (Reference Counted) und Arc<T> (Atomic Reference Counted) ist .clone() billig — es erhöht nur den Reference-Counter, ohne den inneren Wert zu duplizieren.
use std::rc::Rc;
fn main() {
let original = Rc::new(String::from("Hi"));
let klon1 = original.clone(); // Counter: 2
let klon2 = original.clone(); // Counter: 3
println!("Counter: {}", Rc::strong_count(&original));
// Counter: 3
}Alle drei Bindungen zeigen auf denselben String. Beim Drop des letzten wird der String tatsächlich freigegeben.
Hier ist clone() syntaktisch dieselbe Methode wie bei String, hat aber komplett andere Performance-Charakteristik. Wichtig zu wissen, weil .clone() auf Rc keine Tiefe-Kopie produziert. Mehr im Smart-Pointer-Kapitel.
Praxis: Copy/Clone im echten Code
Koordinaten-Tupel als Copy
#[derive(Copy, Clone, Debug, PartialEq)]
struct Koord { x: f64, y: f64 }
fn distanz(a: Koord, b: Koord) -> f64 {
let dx = a.x - b.x;
let dy = a.y - b.y;
(dx * dx + dy * dy).sqrt()
}
fn main() {
let a = Koord { x: 0.0, y: 0.0 };
let b = Koord { x: 3.0, y: 4.0 };
println!("{}", distanz(a, b)); // 5.0
println!("{a:?} {b:?}"); // beide noch nutzbar
}Koord ist klein und enthält nur f64s — Copy ist ideal. Funktionen können Koord by-value annehmen, ohne den Aufrufer zu „enteignen".
Konfiguration mit Clone
#[derive(Clone)]
struct AppConfig {
db_url: String,
workers: u32,
features: Vec<String>,
}
impl AppConfig {
fn default_local() -> Self {
AppConfig {
db_url: "postgres://localhost/dev".into(),
workers: 4,
features: vec!["debug".into()],
}
}
}
fn main() {
let cfg = AppConfig::default_local();
// Per-Worker-Kopie der Config:
for _ in 0..cfg.workers {
let worker_cfg = cfg.clone();
// ... spawn worker mit worker_cfg ...
let _ = worker_cfg;
}
}Mehrere Worker brauchen jeweils ihre eigene Config. Clone macht das explizit — jeder Worker hat seine unabhängige Kopie, mit eigenem Heap-Speicher für die Strings.
UUID als Clone, nicht Copy
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
struct UserId(String);
impl UserId {
fn neu(s: &str) -> Self { UserId(s.to_string()) }
}
fn verarbeite(id: UserId) {
// id wird hierhin gemoved — Aufrufer muss bewusst klonen, wenn weiter benötigt
println!("Verarbeite User: {id:?}");
}
fn main() {
let id = UserId::neu("user-42");
verarbeite(id.clone()); // explizit klonen
println!("Original: {id:?}"); // ok
}UserId ist nicht Copy — sie hält einen heap-allokierten String. Clone macht Kopien explizit. Der Aufrufer entscheidet aktiv, wann er klonen will.
Numerisches Tupel-Newtype als Copy
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
struct Meter(f64);
impl Meter {
fn plus(self, andere: Meter) -> Meter { Meter(self.0 + andere.0) }
}
fn main() {
let strecke = Meter(100.0);
let gesamt = strecke.plus(Meter(50.0)).plus(Meter(30.0));
println!("{:?}", gesamt); // Meter(180.0)
// strecke noch nutzbar — ist Copy.
}Newtype-Pattern über f64. Da f64 Copy ist, kann auch Meter Copy sein — und sollte es auch sein, damit die API natürlich zu schreiben ist.
Cache-Eintrag mit selektivem Clone
use std::collections::HashMap;
#[derive(Clone)]
struct CacheEintrag {
payload: Vec<u8>,
erstellt_ts: u64,
}
struct Cache {
eintraege: HashMap<String, CacheEintrag>,
}
impl Cache {
// Gibt eine geklonte Kopie — Aufrufer kann frei damit arbeiten,
// ohne den Cache zu blockieren.
fn holen(&self, key: &str) -> Option<CacheEintrag> {
self.eintraege.get(key).cloned()
}
}.cloned() auf einem Option<&T> ist ein häufiges Pattern — es ruft clone() auf dem inneren T und liefert ein Option<T>. Funktioniert nur, wenn T: Clone.
Builder mit Move + Clone-Möglichkeit
#[derive(Clone)]
struct AnfrageTemplate {
url: String,
standard_header: Vec<(String, String)>,
}
impl AnfrageTemplate {
fn mit_header(mut self, key: &str, wert: &str) -> Self {
self.standard_header.push((key.into(), wert.into()));
self
}
}
fn main() {
let template = AnfrageTemplate {
url: "https://api.example.com".into(),
standard_header: vec![("Accept".into(), "application/json".into())],
};
// Template mehrfach klonen, jeweils anpassen
let a = template.clone().mit_header("X-Request-Id", "1");
let b = template.clone().mit_header("X-Request-Id", "2");
let _ = (a.url, b.url);
}Builder-Pattern + Clone macht „Template mit Variationen" trivial.
Bytes-Slice mit Clone-Vermeidung
// Wenn die Funktion nur liest, KEIN clone nötig — &-Borrow reicht:
fn checksum(daten: &[u8]) -> u32 {
daten.iter().map(|&b| b as u32).sum()
}
fn main() {
let buffer = vec![1u8, 2, 3, 4, 5];
let s1 = checksum(&buffer); // kein clone, kein move
let s2 = checksum(&buffer); // funktioniert problemlos
println!("{s1} {s2}");
}Wichtige Regel: wenn Borrow reicht, kein Clone. .clone() ist Heap-Allocation — die teuerste Operation in vielen Programmen.
Snapshot-Konfiguration mit Arc
use std::sync::Arc;
#[derive(Debug)]
struct GroßeConfig {
// ... viele große Felder ...
grosse_string_liste: Vec<String>,
}
fn main() {
let cfg = Arc::new(GroßeConfig {
grosse_string_liste: vec!["x".into(); 10_000],
});
// Statt cfg.clone() (=20_000 Heap-Allocations):
let cfg_kopie_1 = Arc::clone(&cfg); // nur Counter ++
let cfg_kopie_2 = Arc::clone(&cfg); // nur Counter ++
// Alle drei zeigen auf DIESELBE Config.
let _ = (cfg, cfg_kopie_1, cfg_kopie_2);
}Bei großen Strukturen, die zwischen Threads geteilt werden, ist Arc::clone billig — es kopiert nur einen Pointer und inkrementiert einen Counter. Mehr im Smart-Pointer-Kapitel.
Besonderheiten
Copy impliziert Clone.
Jeder Copy-Typ ist auch Clone. Wer #[derive(Copy, Clone)] schreibt, hat beides — der Compiler verlangt explizit beides nebeneinander. Reines #[derive(Copy)] ohne Clone ist Compile-Fehler.
Copy ist nicht „free“ — nur „günstig“.
Eine Bit-Kopie kostet, abhängig von der Typ-Größe: 4 Bytes (i32) sind eine Register-Bewegung. 4 KB ([u8; 4096]) sind nicht trivial. Wer Copy auf große Structs setzt, verteilt versteckte Kopien überall. Faustregel: max. 16 Bytes (zwei Register) als Copy.
Clone sagt nichts über die Tiefe der Kopie.
Bei String, Vec ist .clone() eine tiefe Kopie inkl. Heap-Duplizierung. Bei Rc, Arc ist es eine flache Operation, die nur den Counter erhöht. Beide implementieren denselben Trait — die Performance ist drastisch unterschiedlich.
derive(Copy) verlangt alle Felder als Copy.
Bei verschachtelten Structs propagiert das: struct Outer { inner: Inner } kann nur Copy sein, wenn Inner Copy ist. Sobald irgendwo ein String oder Vec auftaucht, ist die Copy-Kette gebrochen.
Cell ist Copy wenn T: Copy.
Cell<i32> ist Copy, weil sein Inhalt i32 Copy ist und Cell selbst kein Drop hat. Wichtig für Interior-Mutability-Patterns auf Stack-only-Daten.
.clone() auf Option ist T.clone().
Option<T>: Clone wenn T: Clone. Some(x).clone() ergibt Some(x.clone()). None.clone() ergibt None. Sehr nützlich bei Funktions-Rückgaben mit Option-Wrapper.
String::from(s) klont, String::from(&s) auch.
Es gibt mehrere äquivalente Wege, einen &str in einen String zu konvertieren — alle allokieren auf dem Heap: s.to_string(), s.to_owned(), String::from(s), s.into(). Wahl nach Lesbarkeit, Performance identisch.
Trait-Hierarchien und Default-Derives.
Häufige Derive-Kombination für „Wert-artige" Typen: #[derive(Debug, Clone, PartialEq, Eq, Hash)]. Für triviale: zusätzlich Copy. Für sortierbar: PartialOrd, Ord. Für serde: Serialize, Deserialize. Wer alles will, bekommt schnell eine lange Derive-Liste — das ist okay.
Weiterführende Ressourcen
Externe Quellen
- The Rust Book – Ways Variables and Data Interact: Clone
- std::marker::Copy
- std::clone::Clone
- Rust Reference – Copy trait
- Rust API Guidelines – Conventional Trait Implementations