Zum Inhalt springen

Rust-Fehler E0716: temporary value dropped while borrowed (temporärer Wert)

E0716 bedeutet: Du hast dir etwas von einem Wert ohne Variablennamen ausgeliehen und diesen Borrow dann in einer späteren Zeile weiter benutzt. Solche namenlosen Werte wirft Rust am Ende der Anweisung weg, also würde der Borrow ins Leere zeigen. Das sieht beängstigend aus, ist aber einer der am leichtesten zu behebenden Borrow-Fehler: Meistens teilst du einfach eine Zeile in zwei auf. (Diese Seite gehört zu Rust-Compilerfehler erklärt.)

error[E0716]: temporary value dropped while borrowed
--> src/main.rs:2:13
|
2 | let s = String::from("hi").as_str(); // s leiht sich einen String ohne Namen aus
| ^^^^^^^^^^^^^^^^^^ - temporary value is freed at the end of this statement
| |
| creates a temporary value which is freed while still in use
3 | println!("{s}"); // ❌ dieser String ist schon weg
| - borrow later used here
|
help: consider using a `let` binding to create a longer lived value
|
2 ~ let binding = String::from("hi");
3 ~ let s = binding.as_str(); // s leiht sich einen String ohne Namen aus
|

Auf Deutsch heißt das: „Ein temporärer Wert wurde gedroppt (aufgeräumt), während er noch ausgeliehen war.“ Dieses Programm erzeugt die Meldung:

fn main() {
let s = String::from("hi").as_str(); // s leiht sich einen String ohne Namen aus
println!("{s}"); // ❌ dieser String ist schon weg
}

Ein temporärer Wert (temporary) ist ein Wert, der mitten in einem Ausdruck entsteht, aber nie in einer Variablen gespeichert wird. In der Zeile oben baut String::from("hi") einen echten String, aber niemand besitzt ihn: Es gibt kein let name = ... dafür. Rust droppt einen temporären Wert am Ende der Anweisung, also beim ;.

Lies die drei Markierungen von links nach rechts:

  1. ^^^^ unter String::from("hi"): Hier entsteht ein temporärer String (creates a temporary value).
  2. - am Ende der Zeile: Hier, beim ;, wird dieser String freigegeben (freed at the end of this statement).
  3. - unter s in Zeile 3: Aber s (ein &str, das in diesen String zeigt) wird hier noch benutzt (borrow later used here).

Stell dir einen Kellner vor, der deine Bestellung auf einen Klebezettel schreibt und den Zettel wegwirft, sobald er weggeht. Sagst du einer Freundin „die Bestellung steht auf dem Zettel“, findet sie nichts. Du musst den Zettel selbst behalten oder die Bestellung irgendwo aufschreiben, wo sie bleibt.

Der Teil help: der Meldung ist meistens schon die Antwort: Leg eine Variable an (rustc nennt sie binding), damit der Wert einen Besitzer hat, der länger lebt.

Das ist dieselbe Regel wie bei E0597: Eine Referenz darf nie länger leben als der Wert, auf den sie zeigt. Sonst wird sie zu einer hängenden Referenz (dangling reference) auf freigegebenen Speicher. Der einzige Unterschied: Bei E0597 hat der Wert einen Namen und stirbt an einer }. Bei E0716 hat der Wert keinen Namen und stirbt am ;.

Einen temporären Wert in derselben Anweisung zu benutzen, ist völlig in Ordnung. println!("{}", String::from("hi").as_str()); kompiliert, weil der Borrow vor dem ; endet. Der Fehler tritt nur auf, wenn der Borrow in einer späteren Zeile noch gebraucht wird. Siehe Wann endet ein Borrow? und Lifetimes.

Es gibt eine hilfreiche Ausnahme, die Verlängerung der Lebensdauer von Temporären (temporary lifetime extension). Schreibst du & (oder &mut) direkt vor den temporären Wert in einem let, hält Rust den Wert stillschweigend so lange am Leben wie die Variable. Deshalb kompilieren diese Zeilen:

struct Label<'a> {
text: &'a str,
}
fn make_title() -> String {
String::from("Posteingang")
}
fn main() {
let greeting = &String::from("hi"); // ✅ verlängert: & direkt im let
let queue = &mut vec![1, 2, 3]; // ✅ verlängert
queue.push(4);
let label = Label { text: &make_title() }; // ✅ verlängert: & direkt in einem Struct-Literal
println!("{greeting} {queue:?} {}", label.text);
}

Ausgabe:

hi [1, 2, 3, 4] Posteingang
▶ Selbst ausprobieren

Die Verlängerung wirkt nicht durch einen Methoden- oder Funktionsaufruf hindurch. String::from("hi").as_str(), make_title().as_str(), vec![1, 2].iter() und shout(&make_title()) leihen sich den temporären Wert alle innerhalb eines Aufrufs aus. Der Wert stirbt also trotzdem beim ;, und du bekommst E0716. Du musst dir nicht jeden Fall merken: Siehst du E0716, gib dem Wert einen Namen.

Lösung 1: Dem temporären Wert mit let einen Namen geben

Abschnitt betitelt „Lösung 1: Dem temporären Wert mit let einen Namen geben“

Nimm diese Lösung fast immer. Der Compiler schlägt sie vor, und bei den meisten E0716-Fehlern ist sie die richtige Antwort. Speichere den Wert zuerst in einer eigenen Variablen und leih dir in der nächsten Zeile etwas von dieser Variablen aus.

Ein sehr häufiger Fall ist das Zerlegen einer Eingabezeile. read_command() gibt einen neuen String zurück, .trim() und .split(' ') leihen sich etwas davon aus, und der Vec<&str> behält diese Borrows:

let parts: Vec<&str> = read_command().trim().split(' ').collect(); // ❌ E0716

Rust zeigt auf read_command() und sagt, dass der Wert am Ende der Anweisung freigegeben wird. Gib ihm einen Namen:

fn read_command() -> String {
// In einem echten Programm würde das eine Zeile von stdin lesen.
String::from(" add milk eggs \n")
}
fn main() {
let line = read_command(); // der String lebt jetzt bis zum Ende von main
let parts: Vec<&str> = line.trim().split(' ').collect();
println!("{parts:?}");
println!("Befehl: {}, Einträge: {}", parts[0], parts.len() - 1);
}

Ausgabe:

["add", "milk", "eggs"]
Befehl: add, Einträge: 2
▶ Selbst ausprobieren

Die Eingabe add milk eggs bleibt auf Englisch, weil sie wie ein Befehl in einem Programm aussieht – so, wie ein Nutzer ihn eintippen würde.

Genau das passiert dir auch mit echter Eingabe: lines.next().unwrap().unwrap().trim().split(' ').collect() auf io::stdin().lines() ergibt aus demselben Grund E0716. Schreib zuerst let line = lines.next().unwrap().unwrap(); in eine eigene Zeile. Siehe Strings.

Nimm diese Lösung, wenn der Borrow länger leben muss als jede Variable, die du anlegen könntest – zum Beispiel in einem Struct, das du weiterreichst, oder in einem Vec, den du in einer Schleife füllst. Ein let innerhalb einer Schleife würde am Ende jedes Durchlaufs gedroppt. Damit würde aus E0716 nur E0597.

Hier sind zwei kaputte Versionen. Ein Struct, das ein &str aus einem temporären Wert hält, und eine Schleife, die Borrows neuer Strings einfügt:

let label = Label { text: make_title().as_str() }; // ❌ E0716
let mut names: Vec<&str> = Vec::new();
for name in ["ferris", "corro"] {
names.push(name.to_uppercase().as_str()); // ❌ E0716
}

Die Lösung: Lass das Struct und den Vec ihre Strings besitzen. Nimm String statt &str und lass das .as_str() weg:

struct Label {
text: String, // eigener String, also keine Lifetime nötig
}
fn make_title() -> String {
String::from("Posteingang")
}
fn main() {
let label = Label { text: make_title() }; // der String wird in das Label verschoben
println!("Label: {}", label.text);
let mut names: Vec<String> = Vec::new();
for name in ["ferris", "corro"] {
names.push(name.to_uppercase()); // der Vec besitzt jeden neuen String
}
println!("{names:?}");
}

Ausgabe:

Label: Posteingang
["FERRIS", "CORRO"]
▶ Selbst ausprobieren

Für Einsteiger sind eigene Felder (String, Vec<T>) fast immer die einfachste Wahl. Ein Struct mit einem &str-Feld braucht eine Lifetime und kann nie länger leben als die Daten, die es ausleiht. Siehe Structs, die Referenzen enthalten und Structs.

Nimm diese Lösung, wenn der temporäre Wert der Anfang einer Iterator-Kette ist. Diese Zeilen erzeugen einen Vec mit vec![...], leihen ihn mit .iter() aus und behalten den Iterator (oder sein Ergebnis) für später:

let doubled = &mut vec![1, 2, 3].iter().map(|n| n * 10); // ❌ E0716
let best = vec![72, 95, 88].iter().max(); // ❌ E0716

Das &mut davor hilft nicht: Die Verlängerung hält den Iterator am Leben, aber nicht den Vec, von dem .iter() ausleiht. Es gibt zwei Auswege:

  • Nimm into_iter(), damit der Iterator den Vec besitzt (das schlägt auch rustc vor). Dann brauchst du auch kein &mut mehr, nur let mut.
  • Beende die Kette mit einem eigenen Ergebnis, etwa mit .copied(), .cloned(), .sum() oder .collect::<Vec<_>>() aus einfachen Zahlen. Dann leiht nach dem ; nichts mehr etwas vom temporären Wert aus.
fn main() {
// Der Iterator besitzt den Vec, also leiht nichts von einem temporären Wert
let mut doubled = vec![1, 2, 3].into_iter().map(|n| n * 10);
println!("{:?}", doubled.next());
println!("{:?}", doubled.next());
// .copied() macht aus Option<&i32> ein Option<i32>, einen Wert ohne Borrow
let best = vec![72, 95, 88].iter().max().copied();
println!("Bester: {best:?}");
}

Ausgabe:

Some(10)
Some(20)
Bester: Some(95)
▶ Selbst ausprobieren

Brauchst du den Vec später noch einmal, geh zurück zu Lösung 1: let scores = vec![72, 95, 88]; und dann scores.iter().max(). Siehe Iteratoren.

Einen Mutex mitten in einer Kette sperren. let first = names.lock().unwrap().first(); ergibt E0716, weil der Lock-Guard ein temporärer Wert ist. Wird er gedroppt, wird die Sperre freigegeben – also darf first nicht weiter hineinzeigen. Lösung: let guard = names.lock().unwrap(); und dann guard.first(), oder du kopierst die Daten in einem Rutsch heraus, mit .first().cloned(). Siehe Shared State.

&temporärer_wert an eine Funktion übergeben, die eine Referenz zurückgibt. Mit fn shout(s: &str) -> &str ergibt die Zeile let t = shout(&make_title()); E0716: Das zurückgegebene &str leiht sich etwas vom temporären String aus, und die Verlängerung reicht nicht durch einen Funktionsaufruf hindurch. Lösung: let title = make_title(); und dann shout(&title).

Ihn aus einer Funktion zurückgeben. Schreibst du make_title().as_str() als letzte Zeile einer Funktion, die eine Referenz zurückgibt (etwa -> &'static str), bekommst du stattdessen E0515 („cannot return value referencing temporary value“, ein Wert, der auf einen temporären Wert verweist, kann nicht zurückgegeben werden). Lösung: Gib den String zurück.

Kurzer Check

1. Was ist in Rust ein „temporärer Wert“?

2. let s = String::from("hi").as_str(); gefolgt von println!("{s}"); ergibt E0716. Was ist die übliche Lösung?

3. Welche Zeile kompiliert dank der Verlängerung der Lebensdauer von Temporären?

Punkte: 0 / 3