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:

Rust Copy-Verhalten
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: &T für jedes T (die Referenz selbst, nicht das Ziel).
  • Funktions-Pointer: fn(...).
  • Raw-Pointer: *const T, *mut T.
  • Tupel aus Copy-Typen: (i32, f64, bool) ist Copy.
  • Arrays aus Copy-Typen: [u8; 32] ist Copy.

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 nicht Copy).

Marker-Charakter

Copy hat keine Methoden — er signalisiert dem Compiler nur „dieser Typ darf bit-kopiert werden". Die Copy-Implementierung sieht so aus:

Rust Copy-Definition
// Aus der Stdlib (vereinfacht):
pub trait Copy: Clone {}

Zwei wichtige Eigenschaften:

  • Trait ohne Methoden — pure Markierung.
  • Copy: Clone — jeder Copy-Typ ist auch automatisch Clone. Wer Copy derive-t, muss auch Clone derive-en.

Clone — die explizite Kopie

Clone hat eine Methode, die explizit aufgerufen wird:

Rust Clone-Verhalten
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 s2 mit 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

Rust Clone-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

AspektCopyClone
AufrufImplizit (Compiler)Explizit (.clone())
KostenBit-Kopie (immer billig)Beliebig teuer (Heap-Alloc möglich)
Wo erlaubtNur Stack-Daten ohne DropBeliebige Daten
BeziehungCopy: CloneCopy ist SubsetClone ist breiter
Methodenkeine (Marker)clone(), clone_from()
Drop-KompatibilitätTyp darf keinen Drop habenTyp 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:

Rust Eigene Copy-Typen
#[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:

Rust Mit String-Feld
#[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-CharakteristikEmpfehlung
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

Rust Großer Copy-Type
#[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

Rust 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

Rust Zähler
// #[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:

Rust Custom Clone
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.

Rust Rc-Clone
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

Rust Koordinate
#[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

Rust App-Config
#[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

Rust ID-Typ
#[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

Rust Newtype
#[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

Rust Cache-Lookup
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

Rust Builder, mehrfach verwendet
#[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

Rust Borrow statt Clone
// 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

Rust Arc statt Clone
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

/ Weiter

Zurück zu Ownership

Zur Übersicht