Ownership (Besitz) ist das Herzstück von Rust. Es ersetzt Garbage Collector und manuelle Speicherverwaltung. Drei Regeln:
- Jeder Wert hat genau einen Besitzer (eine Variable).
- Es kann zu einer Zeit nur einen Besitzer geben.
- Wenn der Besitzer den Gültigkeitsbereich verlässt, wird der Wert freigegeben (
drop).
fn main() {
{
let s = String::from("Hallo"); // s besitzt den Text auf dem Heap
println!("{s}");
} // hier endet s: der Speicher wird automatisch freigegeben
}Ausgabe
Hallo
Verschieben (Move)
Wenn du einen Wert zuweist, zieht der Besitz um. Die alte Variable ist danach ungültig:
fn main() {
let a = String::from("Text");
let b = a; // Besitz wandert von a nach b
println!("{b}");
// println!("{a}"); // Fehler: a wurde verschoben
}Ausgabe
Text
fn main() {
let a = String::from("Text");
let b = a;
println!("{a} {b}");
}Fehlermeldung
error[E0382]: borrow of moved value: `a`
Das verhindert doppeltes Freigeben: Es gibt immer nur einen Besitzer, der aufräumt.
Einfache Typen (i32, bool, char, f64, Tupel daraus) werden dagegen kopiert (Copy):
fn main() {
let x = 5;
let y = x; // Kopie: beide gültig
println!("{x} {y}");
let s1 = String::from("tief");
let s2 = s1.clone(); // ausdrückliche tiefe Kopie
println!("{s1} {s2}");
}Ausgabe
5 5 tief tief
Besitz an Funktionen geben
fn verbrauche(s: String) {
println!("verbraucht: {s}");
} // s wird hier freigegeben
fn gib_zurueck(s: String) -> String {
s // Besitz geht zurück an den Aufrufer
}
fn main() {
let a = String::from("eins");
verbrauche(a); // Besitz übergeben
// println!("{a}"); // Fehler: a gehört nicht mehr uns
let b = String::from("zwei");
let b = gib_zurueck(b);
println!("{b}");
}Ausgabe
verbraucht: eins zwei
Ständig Besitz hin- und herzugeben ist umständlich. Dafür gibt es Borrowing.
Borrowing: Ausleihen mit Referenzen
Mit & leihst du einen Wert aus, ohne den Besitz zu übernehmen:
fn laenge(s: &String) -> usize { // leiht aus: nur lesen
s.len()
}
fn anhaengen(s: &mut String) { // leiht veränderlich aus
s.push_str(" Welt");
}
fn main() {
let mut text = String::from("Hallo");
let l = laenge(&text); // text bleibt Besitzer
println!("{text} hat {l} Zeichen");
anhaengen(&mut text);
println!("{text}");
}Ausgabe
Hallo hat 5 Zeichen Hallo Welt
Die Borrowing-Regeln
Zu jedem Zeitpunkt gilt entweder:
- beliebig viele unveränderliche Referenzen (
&T), oder - genau eine veränderliche Referenz (
&mut T)
und Referenzen müssen immer gültig sein (keine hängenden Zeiger).
fn main() {
let mut s = String::from("x");
let r1 = &s;
let r2 = &s; // mehrere Leser: erlaubt
println!("{r1} {r2}");
let w = &mut s; // hier enden r1/r2 schon (letzte Benutzung davor)
w.push('y');
println!("{w}");
}Ausgabe
x x xy
fn main() {
let mut s = String::from("x");
let r1 = &s;
let w = &mut s;
w.push('y');
println!("{r1}");
}Fehlermeldung
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
Diese Regeln verhindern Datenwettläufe schon beim Übersetzen: Niemand kann Daten lesen, während jemand anderes sie ändert.
Slices: Sichten auf Teile
fn erstes_wort(s: &str) -> &str {
s.split_whitespace().next().unwrap_or("")
}
fn main() {
let satz = String::from("Hallo schöne Welt");
println!("{}", erstes_wort(&satz));
let zahlen = [1, 2, 3, 4, 5];
let teil: &[i32] = &zahlen[1..4];
println!("{teil:?} {}", teil.len());
println!("{}", erstes_wort("Literal ist auch &str"));
}Ausgabe
Hallo [2, 3, 4] 3 Literal
Nimm als Parameter &str statt &String und &[T] statt &Vec<T>: Dann funktioniert die Funktion mit mehr Typen.
Lebensdauern (Lifetimes)
Eine Referenz darf nie länger leben als das, worauf sie zeigt. Meist erkennt der Compiler das selbst. Gibt eine Funktion eine Referenz zurück und hat mehrere Eingabereferenzen, musst du sagen, woher sie stammt: mit einer Lebensdauer-Annotation ('a):
fn laengster<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() >= b.len() { a } else { b }
}
struct Auszug<'a> {
text: &'a str, // Struct leiht einen Text aus
}
fn main() {
let x = String::from("kurz");
let ergebnis;
{
let y = String::from("deutlich länger");
ergebnis = laengster(&x, &y).to_string(); // Kopie, bevor y verschwindet
}
println!("{ergebnis}");
let a = Auszug { text: &x[..2] };
println!("{}", a.text);
}Ausgabe
deutlich länger ku
'a heißt: "Die Rückgabe lebt höchstens so lange wie die kürzer lebende der Eingaben." Es ändert nichts am Programm, es erklärt dem Compiler die Beziehung.
fn main() {
let r;
{
let x = 5;
r = &x;
}
println!("{r}");
}Fehlermeldung
error[E0597]: `x` does not live long enough
Umgang mit Besitz im Alltag
| Situation | Lösung |
|---|---|
| Funktion soll nur lesen | &T, &str, &[T] |
| Funktion soll ändern | &mut T |
| Funktion soll Besitz übernehmen | T (z. B. beim Speichern in einem Struct) |
| Wert wird an mehreren Stellen gebraucht | .clone() (Kosten bewusst), oder Rc<T> / Arc<T> (geteilter Besitz) |
| Daten zwischen Threads teilen | Arc<Mutex<T>>, Channels |
Merke
- Jeder Wert hat einen Besitzer; am Ende seines Gültigkeitsbereichs wird er freigegeben
- Zuweisung verschiebt den Besitz (außer bei
Copy-Typen);.clone()kopiert tief - Borrowing:
&T(viele Leser) oder&mut T(ein Schreiber), nie beides gleichzeitig - Slices
&str,&[T]sind Sichten; Lebensdauern ('a) erklären, wie lange Referenzen gültig sind - Der Compiler garantiert dadurch Speichersicherheit ohne Laufzeitkosten
Aufgabe
Schreibe eine Funktion groesstes(liste: &[i32]) -> Option<&i32> und eine Funktion, die ein Vec<String> entgegennimmt, es sortiert und zurückgibt.