Lifetimes in Signaturen

Lifetimes in Rust: wann du sie annotieren musst

Die Angabe beschreibt keine Dauer, sondern eine Beziehung zwischen zwei Referenzen. Wer das einmal sortiert hat, schreibt sie in drei klar erkennbaren Fällen und sonst nie.

KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Seit 1997 am Markt Kleine Gruppen Präsenz und Live-Online Zertifizierte Trainer

Kurz gesagt

Eine Lifetime sagt nicht, wie lange ein Wert lebt, sondern wie lange eine Referenz gültig bleiben muss. In den meisten Signaturen ergänzt der Compiler diese Angabe selbst, das heißt Elision. Hinschreiben musst du sie in drei Situationen: wenn mehrere Eingabe-Referenzen vorliegen und eine Referenz zurückkommt, wenn eine Struktur eine Referenz speichert, und wenn ein Thread oder eine Aufgabe eine Zusage über die Haltbarkeit verlangt.

Stand dieser Seite: 05.10.2026

01 Worum es geht

Die Schreibweise sieht nach Speicherverwaltung aus und meint etwas anderes

Das Hochkomma mit einem Buchstaben dahinter liest sich wie eine Dauer, und entsprechend wird es gelesen: dieser Wert lebt so lange. Das stimmt nicht. Es ist ein Name für einen Bereich des Programms, in dem eine Referenz gültig bleiben muss, und welcher Bereich das konkret ist, entscheidet der Compiler an jeder Aufrufstelle neu. Genau deshalb funktioniert dieselbe Funktion mit einem Wert aus einer Konstanten und mit einem Wert aus einer Schleife.

Aus dem Missverständnis folgt der teure Teil. Wer Angaben verteilt, bis die Meldung verschwindet, bekommt Angaben, die sich ausbreiten. Eine Struktur mit einem Referenzfeld trägt ihren Parameter in jede Funktion, die sie berührt, in jeden Trait, den sie erfüllt, und in jede weitere Struktur, die sie enthält. Zwei Tage später zieht eine Änderung in einem Modul Anpassungen in sechs weiteren nach sich.

Die Alternative ist meistens billiger, als sie aussieht: die Ownership (den Besitz) übernehmen. Ein String statt eines Slice im Feld kostet eine Allokation und nimmt dafür eine ganze Reihe von Folgeentscheidungen aus dem Entwurf. Referenzen in Feldern lohnen sich, wenn du in einer engen Schleife das Kopieren wirklich vermeiden musst, und das ist deutlich seltener der Fall, als der Reflex nahelegt.

KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt

02 Der Aufbau im Detail

Eine Signatur mit Lifetimes, Stück für Stück

Die Funktion gibt den längeren von zwei Textausschnitten zurück und schreibt nebenbei einen Vermerk in ein Protokoll. Genau an dieser Mischung lässt sich zeigen, welche Referenz eine Angabe braucht und welche nicht.

Der Aufbau

fn longest<'a>(left: &'a str, right: &'a str, log: &mut Vec<String>) -> &'a str
  1. 01 Der Lifetime-Parameter <'a>

    Das ist eine Deklaration wie ein generischer Typparameter, keine Zeitangabe. Der Name ist frei wählbar, 'a ist nur Gewohnheit. Die Zeile sagt: Diese Funktion arbeitet für irgendeine Lifetime, und welche das ist, entscheidet der Aufrufer.

  2. 02 Die erste geliehene Eingabe left: &'a str

    Die Referenz trägt den Parameter. Gemeint ist nicht, dass der Wert genau so lange lebt, sondern mindestens so lange. Ein Slice aus einer Konstanten erfüllt das genauso wie einer aus einem String, der zwei Zeilen weiter unten endet.

  3. 03 Die zweite Eingabe mit derselben Marke right: &'a str

    Hier steckt die eigentliche Aussage. Weil beide Eingaben dieselbe Marke tragen, setzt der Compiler an der Aufrufstelle die kürzere der beiden tatsächlichen Lifetimes ein. Deshalb ist das Ergebnis nur so lange gültig wie die kurzlebigere der beiden Eingaben.

  4. 04 Die Referenz ohne eigene Marke log: &mut Vec<String>

    Diese Referenz taucht im Rückgabetyp nicht auf, deshalb braucht sie keine Angabe. Der Compiler vergibt ihr eine eigene, von 'a unabhängige Lifetime. Das ist der Normalfall: Annotiert wird nur, was mit dem Ergebnis zusammenhängt.

  5. 05 Der Rückgabewert -> &'a str

    Erst diese Marke macht die ganze Angabe nötig. Ohne sie könnte der Compiler nicht wissen, ob die zurückgegebene Referenz in left oder in right zeigt, und damit auch nicht, wie lange sie gültig bleibt. Genau das ist die Frage, die eine Lifetime beantwortet.

Wenn es nicht funktioniert

Das siehst du

Eine Funktion gibt eine Referenz zurück, und der Compiler verlangt eine Lifetime-Angabe (missing lifetime specifier, Fehlercode E0106).

Warum

Es gibt mehr als eine Eingabe-Referenz, und der Compiler kann nicht erraten, aus welcher der Rückgabewert stammt. Bei nur einer Eingabe-Referenz vergibt er die Angabe von selbst.

Was hilft

Wenn das Ergebnis aus beiden Eingaben stammen kann, markiere beide und den Rückgabewert mit demselben Parameter. Stammt es immer nur aus einer, markiere allein diese, dann ist der Rückgabewert nur an sie gebunden und nicht zusätzlich an die kürzere zweite.

Das siehst du

Der Compiler meldet, ein geliehener Wert lebe nicht lange genug, und zeigt auf eine Variable, die in einem inneren Block angelegt wurde.

Warum

Die Referenz soll den Block überleben, in dem der Wert selbst liegt. Am Ende des Blocks wird der Wert freigegeben, die Referenz zeigte danach ins Leere.

Was hilft

Den Wert eine Ebene weiter außen anlegen, damit er länger lebt als die Referenz auf ihn. Wenn das nicht geht, gib statt einer Referenz den Wert selbst zurück, also einen eigenen String statt eines Slices.

Das siehst du

Eine Struktur bekommt ein Feld mit einer Referenz, und plötzlich verlangt jede Verwendung dieser Struktur einen zusätzlichen Parameter.

Warum

Sobald eine Struktur eine Referenz speichert, wird sie selbst über eine Lifetime parametrisiert. Der Parameter zieht sich durch jede Funktion, jede weitere Struktur und jeden Trait, in dem sie vorkommt.

Was hilft

In fast allen Fällen ist es billiger, die Ownership zu übernehmen, also einen eigenen String statt eines Slices und einen eigenen Vec statt einer geliehenen Folge. Referenzen in Feldern lohnen sich nur bei kurzlebigen Hilfstypen, etwa einem Parser, der über einen Puffer läuft und ihn nicht überlebt.

Das siehst du

Ein Aufruf verlangt die statische Lifetime, obwohl die Daten offensichtlich vorhanden sind, etwa beim Starten eines Threads oder einer Aufgabe.

Warum

Die statische Zusage bedeutet nicht, dass der Wert ewig lebt, sondern dass er keine Referenz auf etwas Kurzlebigeres enthält. Ein Thread kann länger laufen als die Funktion, die ihn gestartet hat, deshalb darf er nichts mitnehmen, was vorher endet.

Was hilft

Die Daten in den Thread hineingeben statt sie auszuleihen, also mit move übertragen und bei Bedarf vorher klonen. Für geteilte Daten über Threads hinweg Arc verwenden, das erfüllt die Bedingung, weil der Wert lebt, solange irgendein Arc darauf zeigt. Wenn der Thread garantiert vor den Daten endet, erlauben Scoped Threads über std::thread::scope auch das Ausleihen.

Das siehst du

Eine Methode gibt eine Referenz zurück, die Signatur wird angenommen, aber im Rumpf heißt es, ein Wert lebe nicht lange genug, oder das Ergebnis lässt sich an der Aufrufstelle kürzer verwenden als erwartet.

Warum

Bei Methoden mit einer Referenz auf sich selbst vergibt der Compiler die Lifetime des Rückgabewerts automatisch aus dieser Referenz, auch wenn das Ergebnis in Wahrheit aus einem Parameter stammt. Er fragt deshalb nicht nach, sondern bindet still an das falsche Objekt, und der Fehler taucht eine Ebene später auf.

Was hilft

Den betreffenden Parameter und den Rückgabewert mit demselben Lifetime-Parameter markieren, den du an der Methode einführst. Damit überschreibst du die dritte Regel ausdrücklich. Stammt das Ergebnis tatsächlich aus dem Objekt, lässt du die Angabe weg und verlässt dich auf die Regel.

03 Was du mitnimmst

Was du danach ohne Ausprobieren entscheidest

Es gibt genau drei Regeln, nach denen der Compiler ergänzt, und drei Situationen, in denen er es nicht kann. Alles andere ist Folgeentscheidung: Ownership oder Referenz, Index oder Zeiger, geteilte Ownership oder Durchreichen.

Fünf Fragen, und du weißt, ob eine Angabe fehlt

  1. 01 Gibt die Funktion überhaupt eine Referenz zurück? Sonst brauchst du keine Angabe.
  2. 02 Kommt die Referenz nur aus einer Eingabe? Dann ergänzt der Compiler sie selbst.
  3. 03 Steht eine Referenz auf sich selbst in der Signatur? Dann erbt die Ausgabe deren Dauer.
  4. 04 Speichert eine Struktur eine Referenz? Dann wird die Struktur selbst parametrisiert.
  5. 05 Startet ein Thread oder eine abgegebene Aufgabe? Dann verlangt der Compiler in der Regel die statische Zusage.

Die drei Elisionsregeln kennen

Jeder Referenzparameter bekommt eine eigene Lifetime. Gibt es genau eine Eingabe-Lifetime, geht sie an alle Ausgaben. Steht eine Referenz auf sich selbst in der Signatur, geht deren Lifetime an alle Ausgaben. Was danach noch offen ist, musst du schreiben, und was nicht offen bleibt, schreibst du besser nicht.

Erkennen, wann eine Angabe wirklich fehlt

Hingeschrieben wird eine Lifetime an zwei Stellen: wenn eine zurückgegebene Referenz aus mehr als einer Eingabe stammen könnte, und wenn ein Typ selbst eine Referenz speichert. Der dritte Fall, die statische Zusage bei Threads und Aufgaben, ist keine Annotation, sondern eine Bedingung an den Typ, und sie wird auch anders gelöst. Wenn keine Referenz zurückkommt, ist die Frage gar nicht gestellt.

Ownership statt Referenz im Feld

Für Typen, die länger leben als ein einzelner Funktionsaufruf, ist die Ownership die Voreinstellung. Ein eigener String oder Vec im Feld kostet eine Speicheranforderung und erspart dir den Parameter, der sich sonst durch das halbe Modul zieht.

Die statische Lifetime richtig lesen

Sie bedeutet nicht ewig, sondern dass der Wert keine Referenz auf etwas Kurzlebigeres enthält. Ein eigener String erfüllt das, ein Slice aus einem lokalen Puffer nicht. Ein Thread verlangt sie, weil er länger laufen kann als die Funktion, die ihn gestartet hat.

Index statt Zeiger bei Geflechten

Bei Bäumen und Graphen ist die Antwort nie eine Annotation, sondern ein anderer Aufbau: alle Knoten in einem Vec, Kanten als Zahlen. Das erspart dir sämtliche Lifetime-Fragen und macht die Struktur nebenbei serialisierbar.

Geteilte Ownership statt Borrows über Grenzen

Wenn mehrere Stellen dieselben Daten über Thread-Grenzen hinweg brauchen, beantwortet Arc die Haltbarkeitsfrage ohne jede Annotation: Der Wert lebt, solange irgendein Arc darauf zeigt.

KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt

Was die Marke tatsächlich beschreibt

Eine Lifetime ist ein Name für einen Bereich des Programms. Der Compiler setzt an jeder Aufrufstelle den konkreten Bereich ein, der alle Bedingungen erfüllt. Tragen zwei Parameter dieselbe Marke, muss der eingesetzte Bereich zu beiden passen, also nimmt der Compiler den kürzeren. Genau daraus folgt die häufigste Überraschung: Das Ergebnis lässt sich nur dort verwenden, wo auch die kurzlebigere der beiden Eingaben noch gültig ist, selbst wenn es tatsächlich in die langlebigere zeigt.

Der Ausweg ist keine Umschreibung, sondern eine ehrlichere Signatur. Wenn das Ergebnis immer aus der ersten Eingabe stammt, bekommen beide Eingaben eigene Parameter, und nur die erste teilt ihren mit dem Rückgabewert. Der Compiler weiß dann, dass die zweite Eingabe nichts zur Haltbarkeit beiträgt, und die Einschränkung an der Aufrufstelle fällt weg.

Ein Detail, das die Sache entspannt: Lifetimes existieren nur während des Kompilierens. Sie erzeugen keinen Code und keine Daten, das fertige Programm ist identisch mit einem, das ohne Angaben auskommt. Was etwas kostet, ist die Alternative, für die man sich entscheidet, um sie zu vermeiden, also die Speicheranforderung für einen eigenen String.

Elision: die drei Regeln, nach denen der Compiler ergänzt

Regel eins: Jeder Referenzparameter, bei dem keine Angabe steht, bekommt eine eigene. Regel zwei: Gibt es danach genau eine Eingabe-Lifetime, wird sie allen offenen Ausgaben zugewiesen. Regel drei: Ist einer der Parameter eine Referenz auf das Objekt selbst, wird deren Lifetime allen offenen Ausgaben zugewiesen, und zwar unabhängig davon, wie viele andere Referenzparameter es gibt. Bleibt danach eine Ausgabe ohne Zuweisung, bekommst du die Meldung über die fehlende Angabe.

Diese drei Regeln erklären die gesamte Alltagserfahrung. Eine Funktion mit einem Textausschnitt hinein und einem hinaus braucht nie eine Angabe, weil Regel zwei greift. Eine Methode, die einen Verweis auf ein eigenes Feld zurückgibt, braucht nie eine, weil Regel drei greift. Und die Funktion mit zwei Eingaben und einer Ausgabe braucht immer eine, weil nach Regel eins zwei Lifetimes im Spiel sind und keine weitere Regel entscheidet.

Wichtig ist die Grenze: Diese Regeln gelten für Signaturen von Funktionen und Methoden, nicht für Strukturdefinitionen. Ein Referenzfeld verlangt immer einen ausgeschriebenen Parameter, es gibt dort nichts zu ergänzen. Wenn du nur ausdrücken willst, dass eine Lifetime vorhanden ist, ohne ihr einen Namen zu geben, gibt es dafür die anonyme Schreibweise mit dem Unterstrich, und sie liest sich besser als ein erfundener Buchstabe.

Referenzen in Strukturen und warum sie ansteckend sind

Sobald ein Feld eine Referenz enthält, ist die Struktur an die geliehenen Daten gebunden und kann sie nicht überleben. Der Parameter erscheint danach in jeder Funktionssignatur, die den Typ berührt, in jeder Trait-Implementierung und in jeder weiteren Struktur, die ihn als Feld enthält. Zusätzlich fällt eine ganze Reihe von Möglichkeiten weg: Ein solcher Typ lässt sich nicht in einen Thread schicken, der länger lebt, und nicht in einem langlebigen Behälter ablegen.

Richtig ist das Muster bei kurzlebigen Hilfstypen. Ein Parser, der über einen Puffer läuft und Slices daraus zurückgibt, spart damit das Kopieren jedes einzelnen Felds, und er wird ohnehin freigegeben, bevor der Puffer verschwindet. Eine Sicht auf einen Datensatz, die nur für die Dauer einer Auswertung existiert, fällt in dieselbe Kategorie.

Falsch ist es bei Fachtypen, Konfiguration und allem, was gespeichert, verschickt oder über eine Modulgrenze gereicht wird. Wenn du zwischen beiden Welten sitzt, gibt es einen Mittelweg: einen Typ, der entweder ausleiht oder besitzt und sich erst dann für eine eigene Kopie entscheidet, wenn tatsächlich geändert wird. Die Standardbibliothek bringt ihn als Cow mit, und er ist an genau den Stellen sinnvoll, an denen die meisten Aufrufe nur lesen.

Die statische Lifetime ist keine Ewigkeit

Es gibt zwei Verwendungen, die regelmäßig verwechselt werden. Als Bedingung an einen Typ bedeutet die statische Lifetime: Dieser Typ enthält keine Referenzen mit kürzerer Haltbarkeit. Ein eigener String erfüllt das, obwohl er in zwei Zeilen freigegeben wird, denn er leiht sich nichts aus. Als Lifetime einer Referenz bedeutet sie dagegen: Diese Daten liegen für die gesamte Laufzeit des Programms bereit, etwa ein Textliteral, das im Programm selbst steht.

In der Praxis begegnet dir die Bedingung beim Starten von Threads und beim Übergeben von Aufgaben an eine Laufzeitumgebung. Der Grund ist immer derselbe: Der Compiler weiß nicht, wann der Thread endet, also darf nichts hineinwandern, das vorher verschwindet. Die Lösung ist fast nie eine Annotation, sondern eine Entscheidung über die Ownership, also Daten mit move übertragen, vorher klonen oder über Arc teilen.

Die Ausnahme sind Scoped Threads über std::thread::scope. Sie garantieren, dass alle gestarteten Threads vor dem Ende des umschließenden Bereichs zusammengeführt werden, und deshalb darf innerhalb dieses Bereichs ausgeliehen werden. Das ist der saubere Weg, wenn mehrere Threads gemeinsam über einen vorhandenen Puffer laufen sollen, ohne dass jeder eine eigene Kopie bekommt.

Die Alternativen zur Annotation

Erstens die Ownership übernehmen. Das ist die Antwort auf geschätzt neun von zehn Lifetime-Fragen im Anwendungscode. Der Preis ist eine Speicheranforderung pro Wert, und der ist an den meisten Stellen nicht messbar. Wer daran zweifelt, sollte messen statt schätzen, denn die Vermutung, dass eine Kopie teuer ist, hält im Profil selten stand.

Zweitens Indizes statt Referenzen. Für alles, was auf sich selbst zeigt, also Bäume, Graphen, verkettete Strukturen und Objekte mit Elternverweis. Die Knoten liegen in einem Vec, die Kanten sind Zahlen. Damit verschwindet die Lifetime-Frage vollständig, und die Struktur lässt sich nebenbei speichern und übertragen.

Drittens geteilte Ownership über Rc innerhalb eines Threads und Arc über Threads hinweg. Und für den seltenen Fall, dass eine Struktur wirklich auf ihre eigenen Daten zeigen muss, gibt es fertige Bibliotheken, die diese Konstruktion gekapselt anbieten. Die sind in jedem Fall die bessere Antwort als ein selbst geschriebener unsafe-Block, weil die Zusicherungen dort geprüft und dokumentiert sind.

Dazu passende Kurse

Weil sich diese Entscheidung an eigenem Code schneller klärt als an Beispielen aus dem Buch, gibt es bei cmt Rust-Kurse für Fortgeschrittene.

Wissen prüfen

Wie sicher bist du beim Thema wirklich?

Lesen fühlt sich schnell nach Können an. Ein kurzer Test zeigt dir, was davon schon sitzt und wo sich ein Kurs lohnt. Kostenlos, ohne Anmeldung, mit einer Erklärung zu jeder Antwort.

Stimmen aus den Kursen

Was Teilnehmende über die Kurse in diesem Bereich sagen

Sehr angenehmes, gut vorbereitetes Seminar, kann ich nur empfehlen.
Rust Fortgeschrittenenkurs

05 Fragen

Häufige Fragen

Deine Frage ist nicht dabei? Stell sie uns direkt, wir antworten dir persönlich.

Kostet eine Lifetime-Angabe Laufzeit?
Nein. Lifetimes existieren nur während des Kompilierens, sie erzeugen weder Code noch Daten. Das fertige Programm ist identisch mit einem, in dem der Compiler die Angaben selbst ergänzt hat. Was etwas kostet, ist die Entscheidung, die du triffst, um sie zu umgehen, also die Speicheranforderung für einen eigenen Wert statt einer Referenz.
Wann nehme ich die anonyme Schreibweise statt eines Namens?
Wenn es genau eine Lifetime gibt und du dem Compiler nur mitteilen willst, dass eine vorhanden ist, etwa in einem Implementierungsblock oder in einem Rückgabetyp. Sie liest sich besser als ein erfundener Buchstabe und macht sichtbar, dass hier nichts zu entscheiden war.
Warum lässt sich mein Ergebnis nur im inneren Block verwenden?
Weil beide Eingaben denselben Parameter tragen und der Compiler die kürzere der beiden tatsächlichen Lifetimes eingesetzt hat. Wenn das Ergebnis in Wahrheit immer nur aus einer der Eingaben stammen kann, gib jeder Eingabe einen eigenen Parameter und binde die Ausgabe an den der richtigen. Danach verschwindet die Einschränkung.
Brauche ich Lifetimes, wenn ich asynchron schreibe?
Weniger als erwartet, weil abgegebene Aufgaben ohnehin eine statische Zusage verlangen und du deshalb mit eigenen Daten oder mit Arc arbeitest. Wo sie doch auftauchen, ist bei Traits und bei Funktionen, die einen Future zurückgeben, der aus ihren Argumenten ausleiht. Die Meldungen sind dort deutlich länger, und sie enthalten meistens noch Bedingungen an Traits obendrauf.

Passt thematisch dazu

Lifetimes sind die dritte Stufe nach Ownership und Borrowing, deshalb lohnt sich vorher ein Blick auf das Ownership-Modell in seinen Grundzügen.

Persönlich für dich da

Deine Ansprechpartner

Du bist dir nicht sicher, welcher Kurs oder welches Level zu dir passt? Wir beraten dich persönlich und kostenlos.

Yves Hoppe

Yves Hoppe

Weiterbildung & Beratung

Hilft dir, aus dem Rust-Programm den passenden Kurs für deinen Stand zu finden.

Norbert Jansen

Norbert Jansen

Beratung & Inhouse

Plant mit dir Inhouse-Trainings, die auf eure Abläufe und euren Datenbestand zugeschnitten sind.

Lifetimes an einer echten Signatur durchgehen

Im Rust-Aufbaukurs bei cmt nimmst du Signaturen aus deinem eigenen Projekt auseinander und entscheidest pro Fall zwischen Annotation und Ownership, statt beides auszuprobieren.