Rust - Grundlagen - 04 - Besitz

Besitz

Verständnis

Bei Daten auf dem Heap führt let b = a; nur eine flache Kopie aus; a ist danach ungültig und kann nicht mehr verwendet werden (es wurde verschoben, move).

Für eine tiefe Kopie muss let b = a.clone(); geschrieben werden; dann kann a weiter verwendet werden.

Wenn der Parametertyp einer Funktion als String deklariert ist, findet eine flache Kopie statt (der übergebene Wert wird verschoben). Bei einer Referenz (&String) wird nichts verschoben.

(Der Vorgang, eine Referenz zu erzeugen, heißt borrowing.)

Wenn der Wert einer Referenz geändert werden soll, muss der Parameter als &mut String deklariert werden.

Für dieselben Quelldaten darf es zu einem Zeitpunkt entweder nur eine &mut-Referenz (veränderlich) oder mehrere &-Referenzen (unveränderlich) geben.

Nicht erlaubt ist: Im Gültigkeitsbereich der Referenzen existieren gleichzeitig let &b = a und let mut &c = a; unveränderliches und veränderliches Borrowing können nicht gleichzeitig stattfinden.

Referenzen werden zur Kompilierzeit auf Gültigkeit geprüft, daher gibt es keine Dangling References .

Rust besitzt eine spezielle Annotation namens trait; was das genau ist, weiß ich noch nicht.

Quelle

https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html

…

Rust besitzt eine spezielle Annotation namens Copy, die wir auf Typen wie Integer anwenden können, die auf dem Stack gespeichert werden (Traits werden in Kapitel 10 ausführlicher behandelt). Wenn ein Typ den Copy-Trait implementiert, bleibt eine ältere Variable nach einer Zuweisung verwendbar. Rust erlaubt keine Copy-Annotation für einen Typ, wenn der Typ oder einer seiner Bestandteile den Drop-Trait implementiert. Wenn beim Verlassen des Gültigkeitsbereichs etwas Besonderes mit dem Wert geschehen muss und wir diesen Typ mit Copy annotieren, erhalten wir einen Kompilierzeitfehler. Wie man den Copy-Trait für den eigenen Typ implementiert, steht in „Ableitbare Traits“ in Anhang C.

Welche Typen implementieren also den Copy-Trait? In der Dokumentation des jeweiligen Typs lässt sich das sicher prüfen. Als allgemeine Regel gilt: Jede Gruppe einfacher skalarer Werte kann Copy implementieren; nichts, was eine Allokation oder irgendeine Ressource benötigt, kann Copy implementieren. Dazu gehören:

  • alle Integer-Typen wie u32;
  • der boolesche Typ bool mit den Werten true und false;
  • alle Gleitkommatypen wie f64;
  • der Zeichentyp char;
  • Tupel, wenn sie nur Typen enthalten, die ebenfalls Copy implementieren. (i32, i32) implementiert beispielsweise Copy, (i32, String) dagegen nicht.

…

Dateiname: src/main.rs


    let mut s = String::from("hello");

    let r1 = &mut s;
    let r2 = &mut s;

    println!("{}, {}", r1, r2);

Der Fehler lautet:


$ cargo run
   Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0499]: cannot borrow `s` as mutable more than once at a time
 --> src/main.rs:5:14
  |
4 |     let r1 = &mut s;
  |              ------ first mutable borrow occurs here
5 |     let r2 = &mut s;
  |              ^^^^^^ second mutable borrow occurs here
6 |
7 |     println!("{}, {}", r1, r2);
  |                        -- first borrow later used here

For more information about this error, try `rustc --explain E0499`.
error: could not compile `ownership` due to previous error

Der Fehler bedeutet, dass s nicht mehr als einmal gleichzeitig veränderlich geborrowt werden darf. Das erste veränderliche Borrowing steckt in r1 und muss bis zur Verwendung in println! bestehen bleiben. Zwischen Erzeugung und Verwendung wurde jedoch mit r2 eine zweite veränderliche Referenz auf dieselben Daten erstellt.

…

Wie immer können geschweifte Klammern einen neuen Gültigkeitsbereich erzeugen. So sind mehrere veränderliche Referenzen möglich, solange sie nicht gleichzeitig bestehen:


    let mut s = String::from("hello");

    {
        let r1 = &mut s;
    } // r1 endet hier, daher kann problemlos eine neue Referenz erzeugt werden.

    let r2 = &mut s;

Eine ähnliche Regel gilt für die Kombination veränderlicher und unveränderlicher Referenzen. Dieser Code führt zu einem Fehler:


    let mut s = String::from("hello");

    let r1 = &s; // kein Problem
    let r2 = &s; // kein Problem
    let r3 = &mut s; // großes Problem

    println!("{}, {}, and {}", r1, r2, r3);

Der Fehler lautet:


$ cargo run
   Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
 --> src/main.rs:6:14
  |
4 |     let r1 = &s; // no problem
  |              -- immutable borrow occurs here
5 |     let r2 = &s; // no problem
6 |     let r3 = &mut s; // BIG PROBLEM
  |              ^^^^^^ mutable borrow occurs here
7 |
8 |     println!("{}, {}, and {}", r1, r2, r3);
  |                                -- immutable borrow later used here

For more information about this error, try `rustc --explain E0502`.
error: could not compile `ownership` due to previous error

Eine veränderliche Referenz darf also nicht existieren, während eine unveränderliche Referenz aktiv ist. Nutzer einer unveränderlichen Referenz erwarten nicht, dass sich die Werte plötzlich unter ihnen ändern. Mehrere unveränderliche Referenzen sind dagegen unproblematisch, weil niemand, der die Daten nur liest, die Lesevorgänge anderer beeinflussen kann.

Der Gültigkeitsbereich einer Referenz beginnt bei ihrer Einführung und reicht bis zu ihrer letzten Verwendung. Dieser Code wird kompiliert, weil die letzte Verwendung der unveränderlichen Referenzen (println!) vor der Einführung der veränderlichen Referenz liegt:


    let mut s = String::from("hello");

    let r1 = &s; // kein Problem
    let r2 = &s; // kein Problem
    println!("{} and {}", r1, r2);
    // r1 und r2 werden ab hier nicht mehr verwendet

    let r3 = &mut s; // kein Problem
    println!("{}", r3);

…

Die Regeln für Referenzen

Zusammenfassung der Regeln:

  • Zu jedem Zeitpunkt gibt es entweder eine veränderliche Referenz oder beliebig viele unveränderliche Referenzen.
  • Referenzen müssen immer gültig sein.