Sobald ein Struct oder Enum einen Lifetime-Parameter trägt, muss jeder impl-Block diesen Parameter mitführen. Die Syntax ist impl<'a> MyType<'a> — der Lifetime wird in den impl-Klammern deklariert und am Typ aufgeführt. Methoden in solchen impl-Blöcken können zusätzlich eigene Lifetimes haben, und Trait-Implementations bringen weitere Pattern. Dieser Artikel zeigt die Grundsyntax, häufige Fallen und die typischen Pattern beim Schreiben von Methoden für Lifetime-tragende Typen.

Die Grundsyntax

Rust Basic impl mit Lifetime
struct Buffer<'a> {
    data: &'a [u8],
}

impl<'a> Buffer<'a> {
    pub fn neu(data: &'a [u8]) -> Self {
        Buffer { data }
    }

    pub fn len(&self) -> usize {
        self.data.len()
    }

    pub fn first(&self) -> Option<&'a u8> {
        self.data.first()
    }
}

Drei Elemente:

  1. impl<'a> — Lifetime-Parameter wird im impl-Block deklariert
  2. Buffer<'a> — Lifetime am Typ aufgeführt
  3. Methoden können 'a in Signaturen nutzen — z.B. Option<&'a u8> als Rückgabe

Wichtig: das 'a im impl<'a> und das 'a in Buffer<'a> müssen denselben Namen tragen, weil sie dieselbe Lifetime sind.

Self-Lifetime in Methoden

In Methoden bezieht sich &self auf eine Referenz zur Struct-Instanz mit deren Lifetime. Das 'a der Struct-Definition gilt für die internen Refs der Instanz.

Rust Self vs. 'a
# struct Buffer<'a> { data: &'a [u8] }
impl<'a> Buffer<'a> {
    // &self hat seine eigene (kurze) Lifetime, oft elidiert
    // Output kann an 'a gebunden werden — interne Daten
    pub fn data_ref(&self) -> &'a [u8] {
        self.data
    }

    // Output an die kurze self-Lifetime gebunden
    pub fn data_short(&self) -> &[u8] {
        self.data       // Wird elidiert zu &'short [u8]
    }
}

Zwei Outputs mit unterschiedlichen Lifetimes:

  • data_ref(&self) -> &'a [u8] — Output gebunden an die längere 'a-Lifetime (Source-Daten)
  • data_short(&self) -> &[u8] — Output gebunden an die kurze &self-Lifetime (durch Elision)

Bei Wahl 'a darf der Aufrufer den Output über die &self-Borrow-Lifetime hinaus nutzen — solange die ursprünglichen Source-Daten leben.

Methoden mit eigenen Lifetimes

Methoden können zusätzliche Lifetime-Parameter haben.

Rust Method-Lifetime
struct Container<'data> {
    items: Vec<&'data str>,
}

impl<'data> Container<'data> {
    pub fn neu() -> Self {
        Container { items: Vec::new() }
    }

    // Methode mit zusätzlichem Lifetime-Parameter 'short
    pub fn search<'short>(&self, needle: &'short str) -> Option<&'data str> {
        self.items.iter()
            .find(|s| s.contains(needle))
            .copied()
    }
}

search<'short> hat einen eigenen Lifetime-Parameter 'short für das needle-Argument. Die Suche braucht das needle nur kurzfristig — der Output bezieht sich auf 'data (die Container-internen Items).

Mehrere Lifetimes im Typ

Rust Multi-Lifetime-Struct
struct CrossRef<'src, 'dst> {
    source: &'src str,
    destination: &'dst mut String,
}

impl<'src, 'dst> CrossRef<'src, 'dst> {
    pub fn neu(source: &'src str, destination: &'dst mut String) -> Self {
        CrossRef { source, destination }
    }

    pub fn copy(&mut self) {
        self.destination.push_str(self.source);
    }
}

Bei Typen mit mehreren Lifetimes werden alle in impl<'src, 'dst> und am Typ CrossRef<'src, 'dst> aufgeführt.

Trait-Implementations

Trait-Implementations für Lifetime-tragende Typen folgen den gleichen Regeln.

Rust Trait-Impl
use std::fmt::{self, Display};

struct Annotated<'a> {
    value: &'a str,
    label: String,
}

// Trait-Impl mit Lifetime
impl<'a> Display for Annotated<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "[{}] {}", self.label, self.value)
    }
}

impl<'a> Display for Annotated<'a> — Lifetime wird sowohl in impl<'a> als auch am Typ aufgeführt. Das Trait selbst (Display) hat keine Lifetime-Parameter.

Rust Trait mit Lifetime-Parameter
trait Source<'a> {
    fn get(&self) -> &'a str;
}

struct Wrapper<'a> { inner: &'a str }

// Trait-Impl mit zwei Lifetime-Parametern (auch wenn beide gleich)
impl<'a> Source<'a> for Wrapper<'a> {
    fn get(&self) -> &'a str {
        self.inner
    }
}

Wenn das Trait selbst einen Lifetime-Parameter hat, müssen die Lifetimes von Typ und Trait kompatibel sein. Hier wählen wir die gleiche Lifetime 'a für beide.

Anonyme Lifetime '_

In impl-Blöcken kannst du '_ nutzen, wenn der Lifetime-Name nicht in den Methoden verwendet wird.

Rust Anonyme Lifetime
struct Buffer<'a> { data: &'a [u8] }

// Statt impl<'a> Buffer<'a> { ... } wenn 'a in Methoden nicht referenziert wird
impl Buffer<'_> {
    pub fn len(&self) -> usize {
        self.data.len()
    }
}

impl Buffer<'_> sagt: „Buffer mit irgendeinem Lifetime — name brauche ich nicht." Sauberer als impl<'a> Buffer<'a>, wenn 'a im Body nicht erwähnt wird.

Praxis: impl-Pattern in echtem Code

Parser-Struct mit Methoden

Rust Parser-Methoden
pub struct Parser<'src> {
    input: &'src str,
    pos: usize,
}

impl<'src> Parser<'src> {
    pub fn neu(input: &'src str) -> Self {
        Parser { input, pos: 0 }
    }

    // Output an 'src gebunden — Slice in den Source
    pub fn rest(&self) -> &'src str {
        &self.input[self.pos..]
    }

    // Konsumiert ein Zeichen — Output an 'src gebunden
    pub fn next_char(&mut self) -> Option<char> {
        let c = self.input[self.pos..].chars().next()?;
        self.pos += c.len_utf8();
        Some(c)
    }

    // Sucht nach Pattern, gibt Slice zurück
    pub fn consume_until(&mut self, target: char) -> &'src str {
        let start = self.pos;
        while let Some(c) = self.input[self.pos..].chars().next() {
            if c == target { break; }
            self.pos += c.len_utf8();
        }
        &self.input[start..self.pos]
    }
}

Zero-Copy-Parser mit allen Output-Refs an die Source-Lifetime gebunden.

Iterator-Implementation

Rust Iterator-Impl
pub struct Lines<'a> {
    input: &'a str,
    position: usize,
}

impl<'a> Lines<'a> {
    pub fn neu(input: &'a str) -> Self {
        Lines { input, position: 0 }
    }
}

// Iterator-Impl mit Lifetime
impl<'a> Iterator for Lines<'a> {
    type Item = &'a str;

    fn next(&mut self) -> Option<&'a str> {
        if self.position >= self.input.len() { return None; }
        let rest = &self.input[self.position..];
        let line_end = rest.find('\n').unwrap_or(rest.len());
        let line = &rest[..line_end];
        self.position += line_end + 1;
        Some(line)
    }
}

fn main() {
    let text = "first\nsecond\nthird";
    for line in Lines::neu(text) {
        println!("{line}");
    }
}

Custom-Iterator mit Lifetime — Items sind Refs in den Source-String.

Builder mit Method-Chain

Rust Builder
pub struct QueryBuilder<'src> {
    table: &'src str,
    conditions: Vec<&'src str>,
}

impl<'src> QueryBuilder<'src> {
    pub fn neu(table: &'src str) -> Self {
        QueryBuilder { table, conditions: Vec::new() }
    }

    pub fn and(mut self, cond: &'src str) -> Self {
        self.conditions.push(cond);
        self
    }

    pub fn build(&self) -> String {
        let mut q = format!("SELECT * FROM {}", self.table);
        if !self.conditions.is_empty() {
            q.push_str(" WHERE ");
            q.push_str(&self.conditions.join(" AND "));
        }
        q
    }
}

fn main() {
    let q = QueryBuilder::neu("users")
        .and("active = true")
        .and("age > 18")
        .build();
    println!("{q}");
}

Builder mit Lifetime, Method-Chain durch mut self und Self-Return.

Mehrere Lifetimes — Source + Sink

Rust Source-Sink
pub struct Pipeline<'src, 'sink> {
    source: &'src str,
    sink: &'sink mut String,
}

impl<'src, 'sink> Pipeline<'src, 'sink> {
    pub fn neu(source: &'src str, sink: &'sink mut String) -> Self {
        Pipeline { source, sink }
    }

    pub fn transfer(&mut self) {
        self.sink.push_str(self.source);
    }

    pub fn transform<F: Fn(&str) -> String>(&mut self, f: F) {
        self.sink.push_str(&f(self.source));
    }
}

fn main() {
    let src = String::from("hello");
    let mut dst = String::new();
    let mut p = Pipeline::neu(&src, &mut dst);
    p.transform(|s| s.to_uppercase());
    println!("{dst}");        // "HELLO"
}

Pipeline mit unabhängigen Source- und Sink-Lifetimes. Beide werden im impl-Block deklariert.

Anonyme Lifetime im impl

Rust Anonym-Impl
pub struct Reader<'buf> { data: &'buf [u8] }

// Wenn 'buf in Methoden nicht erwähnt wird — '_ ist sauberer
impl Reader<'_> {
    pub fn len(&self) -> usize { self.data.len() }
    pub fn is_empty(&self) -> bool { self.data.is_empty() }
}

Bei trivialen Methoden, die nur &self brauchen, ist impl Reader<'_> lesbarer.

Trait-Impl für Lifetime-tragenden Typ

Rust Display-Impl
use std::fmt::{self, Display};

pub struct Tagged<'a> {
    tag: &'a str,
    value: i32,
}

impl<'a> Display for Tagged<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}={}", self.tag, self.value)
    }
}

fn main() {
    let tag = String::from("count");
    let t = Tagged { tag: &tag, value: 42 };
    println!("{t}");        // "count=42"
}

Display-Trait für Lifetime-Struct. Standard-Pattern für eigene Typen mit Refs.

Generic-Methode in Lifetime-Impl

Rust Generic + Lifetime
pub struct Stream<'a> { data: &'a [u8] }

impl<'a> Stream<'a> {
    // Methode mit Generic-Parameter und eigenem Bound
    pub fn process<F, R>(&self, processor: F) -> R
    where F: FnOnce(&'a [u8]) -> R
    {
        processor(self.data)
    }
}

fn main() {
    let data = vec![1, 2, 3, 4, 5];
    let stream = Stream { data: &data };

    let sum = stream.process(|d| d.iter().map(|&x| x as i32).sum::<i32>());
    println!("Sum: {sum}");
}

Methode mit Generic-Parameter F und Closure-Bound mit 'a-Lifetime. Sehr flexibel — der Aufrufer entscheidet, was mit den Daten geschieht.

Interessantes

impl mit Lifetime: impl<'a> Type<'a>.

Lifetime im impl-Klammer deklariert, am Typ aufgeführt. Beide müssen den gleichen Namen tragen — sind ja dieselbe Lifetime.

&self hat eigene (kurze) Lifetime, oft elidiert.

Bei Methoden ist die self-Lifetime typischerweise kürzer als die Struct-Lifetime. Du kannst sie explizit machen (&'short self) oder elidieren lassen.

Output kann an Struct-Lifetime oder self-Lifetime gebunden sein.

-> &'a T (Struct-Lifetime) oder -> &T (elidiert auf self-Lifetime). Erste Variante ist freier — Aufrufer darf Output länger nutzen.

Methoden können eigene Lifetime-Parameter haben.

fn search<'short>(&self, needle: &'short str) — kurze Lifetime nur für das Argument, Struct-Lifetime weiter aktiv für Output.

Mehrere Lifetimes im Typ: alle im impl-Block deklarieren.

impl<'src, 'dst> CrossRef<'src, 'dst> — beide Parameter müssen genannt werden, sonst Compile-Fehler.

Trait-Impls funktionieren genauso wie inherent Impls.

impl<'a> Display for MyType<'a> — Lifetime an Impl und Typ. Trait selbst kann mit oder ohne eigene Lifetime-Parameter sein.

Anonyme Lifetime '_ für saubere Syntax.

impl Type<'_> wenn die Lifetime im Body nicht referenziert wird. Kürzer und idiomatisch.

Iterator-Impl mit Lifetime — sehr verbreitet.

impl<'a> Iterator for MyIter<'a> mit type Item = &'a T. Standard-Pattern für Zero-Copy-Iteratoren.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Lifetimes

Zur Übersicht