Zum Inhalt springen

Rust-Fehler E0106: missing lifetime specifier (fehlende Lifetime)

E0106 bedeutet: Du hast eine Referenz (&) in den Rückgabetyp einer Funktion oder in ein Struct geschrieben, und Rust kann nicht erkennen, wovon sie sich etwas ausleiht. Das ist kein Fehler in deiner Logik. Der Compiler braucht nur noch ein Wort von dir – oder eine kleine Planänderung. Fast jeder trifft diesen Fehler in seiner ersten Rust-Woche, und die Lösung ist meistens eine einzige Zeile. (Diese Seite gehört zu Rust-Compilerfehler einfach erklärt.)

error[E0106]: missing lifetime specifier
--> src/main.rs:1:32
|
1 | fn longer(a: &str, b: &str) -> &str {
| ---- ---- ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `a` or `b`
help: consider introducing a named lifetime parameter
|
1 | fn longer<'a>(a: &'a str, b: &'a str) -> &'a str {
| ++++ ++ ++ ++

Auf Deutsch heißt das ungefähr: „Es fehlt eine Lifetime-Angabe.“ Der help:-Teil ergänzt: Der Rückgabetyp enthält einen geliehenen Wert, aber die Signatur sagt nicht, ob er von a oder von b geliehen ist.

Dieses Programm erzeugt die Meldung:

fn longer(a: &str, b: &str) -> &str {
if a.len() >= b.len() { a } else { b }
}
fn main() {
println!("{}", longer("apple", "fig"));
}

Eine Referenz ist eine Leihgabe: Sie zeigt auf Daten, die jemand anderem gehören. Immer wenn eine Funktion eine Referenz zurückgibt, muss Rust wissen, in wessen Daten sie zeigt. Nur so kann es prüfen, dass diese Daten noch leben, während der Aufrufer das Ergebnis benutzt.

Schau dir die drei Markierungen unter Zeile 1 an. Der Compiler unterstreicht a und b mit ---- und den Rückgabetyp mit ^. Er sagt damit: „Du gibst einen geliehenen &str zurück, und der könnte aus a oder aus b kommen. Aus welchem?“ Raten will er nicht.

Stell dir einen Bibliotheksausweis vor. Wenn dir ein Freund ein Buch gibt und sagt „das ist geliehen“, willst du wissen, von wem – damit du weißt, wann es zurück muss. Eine Lifetime (Lebensdauer) ist genau dieses Etikett an der Leihgabe.

Rust garantiert, dass eine Referenz nie länger lebt als die Daten, auf die sie zeigt (keine „hängenden Zeiger“). Um das bei einem Funktionsaufruf zu prüfen, schaut sich der Compiler nur die Signatur der Funktion an, nicht ihren Rumpf. Also muss die Signatur selbst sagen, woher die zurückgegebene Referenz stammt.

Meistens schreibst du überhaupt keine Lifetimes, weil Rust sie mit drei einfachen Elision-Regeln (Auslassungsregeln) selbst ergänzt:

  1. Jeder Referenz-Parameter bekommt seine eigene Lifetime.
  2. Gibt es genau einen Referenz-Parameter, leiht sich die zurückgegebene Referenz von ihm.
  3. Ist es eine Methode mit &self oder &mut self, leiht sich die zurückgegebene Referenz von self.

E0106 taucht auf, wenn keine dieser Regeln eine Antwort liefert: bei zwei oder mehr Referenz-Parametern (und ohne self), bei null Referenz-Parametern oder bei einer Referenz in einem Struct. Die Lektion Lifetimes erklärt das ausführlich, und Borrowing erklärt Referenzen an sich.

Lösung 1: Eine benannte Lifetime ('a) hinzufügen

Abschnitt betitelt „Lösung 1: Eine benannte Lifetime ('a) hinzufügen“

Nimm diese Lösung, wenn die Funktion wirklich ein Stück von einer ihrer Eingaben zurückgibt. 'a ist nur ein Name. Steht er an den Eingaben und an der Ausgabe, heißt das: „Das Ergebnis lebt so lange wie diese Eingaben.“

// 'a: Der zurückgegebene &str leiht sich von a oder b, also lebt er so lange wie beide.
fn longer<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() >= b.len() { a } else { b }
}
fn main() {
let first = String::from("apple");
let second = String::from("fig");
println!("Längeres Wort: {}", longer(&first, &second));
}

Ausgabe:

Längeres Wort: apple
▶ Selbst ausprobieren

Die Wörter „apple“ und „fig“ bleiben hier auf Englisch, weil die Funktion ihre Länge in Bytes vergleicht.

Kommt das Ergebnis immer nur aus einem Parameter, schreib 'a nur an diesen Parameter: fn lookup<'a>(map: &'a HashMap<String, String>, key: &str) -> Option<&'a str>. Der Vorschlag des Compilers schreibt 'a überallhin. Das funktioniert, ist aber strenger als nötig.

Lösung 2: Stattdessen einen eigenen String zurückgeben

Abschnitt betitelt „Lösung 2: Stattdessen einen eigenen String zurückgeben“

Nimm diese Lösung, wenn die Funktion neuen Text baut oder wenn der Aufrufer einfach nicht an eine Leihgabe gebunden sein soll. Ein eigener String gehört dem Aufrufer, also gibt es nichts zu verfolgen:

fn greeting(name: &str, lang: &str) -> String {
if lang == "de" {
format!("Hallo, {name}!")
} else {
format!("Hello, {name}!")
}
}
fn main() {
println!("{}", greeting("Ana", "de"));
println!("{}", greeting("Sam", "en"));
}

Ausgabe:

Hallo, Ana!
Hello, Sam!
▶ Selbst ausprobieren

Das kostet eine kleine Speicherreservierung. Für die meisten Programme ist das völlig in Ordnung. Du musst deswegen kein schlechtes Gewissen haben.

Ein Struct-Feld wie title: &str führt zum selben Fehler:

error[E0106]: missing lifetime specifier
--> src/main.rs:2:12
|
2 | title: &str,
| ^ expected named lifetime parameter

Bei Structs ist die beste Lösung fast immer, die Daten selbst zu besitzen: Nimm String statt &str und Vec<T> statt &[T]. Dann kann das Struct so lange leben, wie du willst:

#[derive(Debug)]
struct Book {
title: String, // eigene Daten, kein &str
pages: u32,
}
fn main() {
let book = Book { title: String::from("Dune"), pages: 412 };
println!("{} ({} Seiten)", book.title, book.pages);
}

Ausgabe:

Dune (412 Seiten)
▶ Selbst ausprobieren

Gib einem Struct nur dann eine Lifetime, wenn es eine kurzlebige Ansicht auf Daten ist, die woanders leben – zum Beispiel ein Parser, der sich einen großen Text anschaut. Dann schreibst du <'a> an das Struct und an das Feld:

struct Excerpt<'a> {
text: &'a str, // leiht sich vom Roman unten
}
fn main() {
let novel = String::from("Nennt mich Ismael. Vor einigen Jahren...");
let first = novel.split('.').next().unwrap();
let excerpt = Excerpt { text: first };
println!("Auszug: {}", excerpt.text);
}

Ausgabe:

Auszug: Nennt mich Ismael
▶ Selbst ausprobieren

Hat eine Funktion überhaupt keine Referenz-Parameter, sagt die Meldung „there is no value for it to be borrowed from“ (es gibt keinen Wert, von dem geliehen werden könnte):

error[E0106]: missing lifetime specifier
--> src/main.rs:1:22
|
1 | fn default_name() -> &str {
| ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from

Gibst du nur String-Literale wie "OK" zurück, schreib &'static str. Literale stecken fest in deinem Programm, also leben sie während des ganzen Programmlaufs:

fn status_text(code: u16) -> &'static str {
match code {
200 => "OK",
404 => "Not Found",
_ => "Unknown",
}
}
fn main() {
println!("200 bedeutet {}", status_text(200));
println!("404 bedeutet {}", status_text(404));
}

Ausgabe:

200 bedeutet OK
404 bedeutet Not Found
▶ Selbst ausprobieren

Nimm 'static nur für echte Literale und Konstanten. Wird der Text erst zur Laufzeit gebaut (mit format! oder aus einer Datei gelesen), hilft 'static nicht. Gib dann einen String zurück (Lösung 2).

  • Hilfsfunktionen mit zwei Eingaben, etwa fn lookup(map: &HashMap<String, String>, key: &str) -> Option<&str>. Lösung: Binde die Ausgabe mit 'a nur an map (Lösung 1).
  • Structs, die Text enthalten, etwa ein User { name: &str }, das aus eingelesenen Daten gebaut wird. Lösung: String-Felder (Lösung 3).
  • Funktionen, die feste Meldungen zurückgeben, etwa fn error_label() -> &str. Lösung: &'static str (Lösung 4).

Bei Methoden gibt es gute Nachrichten: fn name(&self) -> &str braucht dank Regel 3 überhaupt keine Lifetime.

Kurzer Check

1. Warum kompiliert fn first_word(s: &str) -> &str ganz ohne Lifetime?

2. Dein Struct Config { path: &str } führt zu E0106. Was ist meistens die beste Lösung?

3. Eine Funktion baut mit format! eine Meldung und gibt sie zurück. Welchen Rückgabetyp sollte sie haben?

Punkte: 0 / 3