Ein Associated Type ist ein Typ, der zur Trait-Implementation gehört. Anders als ein Generic-Parameter ist er pro Implementation fest gewähltIterator for Vec<i32> hat genau einen Item-Typ (i32), nicht beliebig viele. Diese Festlegung macht APIs lesbarer und Type-Inferenz robuster. Der Stdlib-Iterator-Trait ist die Wegweiser-Anwendung: type Item; als Associated Type bedeutet, jeder Iterator-Typ entscheidet, welchen Item-Typ er produziert, und alle Methoden-Signaturen können Self::Item referenzieren.

Syntax

Innerhalb eines Trait-Bodies wird ein Associated Type mit dem type-Keyword deklariert.

Rust Associated Type deklarieren
trait Container {
    type Item;        // Associated Type, von Implementierern zu liefern

    fn get(&self, idx: usize) -> Option<&Self::Item>;
    fn add(&mut self, item: Self::Item);
}

type Item; ist eine Deklaration ohne konkreten Typ — der Implementierer muss den Typ festlegen. Im Trait-Body wird er als Self::Item referenziert.

Rust Implementation
# trait Container {
#     type Item;
#     fn get(&self, idx: usize) -> Option<&Self::Item>;
#     fn add(&mut self, item: Self::Item);
# }
struct StringContainer {
    items: Vec<String>,
}

impl Container for StringContainer {
    type Item = String;        // Item-Typ festlegen

    fn get(&self, idx: usize) -> Option<&String> {
        self.items.get(idx)
    }

    fn add(&mut self, item: String) {
        self.items.push(item);
    }
}

Im impl-Block wird type Item = String; festgelegt. Ab da steht in allen Methoden-Signaturen Self::Item für String.

Associated Type vs. Generic-Parameter

Auf den ersten Blick kann man sich fragen: warum nicht einfach Generic-Parameter? Der Unterschied ist subtil aber wichtig.

Rust Generic-Variante
// Mit Generic-Parameter
trait ContainerG<T> {
    fn get(&self, idx: usize) -> Option<&T>;
    fn add(&mut self, item: T);
}

// Erlaubt mehrere Implementierungen für einen Typ:
struct Multi;
impl ContainerG<String> for Multi { /* ... */ }
impl ContainerG<i32> for Multi { /* ... */ }     // Auch erlaubt
Rust Associated-Type-Variante
// Mit Associated Type
trait ContainerA {
    type Item;
    fn get(&self, idx: usize) -> Option<&Self::Item>;
    fn add(&mut self, item: Self::Item);
}

// Nur EINE Implementation pro Typ:
struct Single;
impl ContainerA for Single {
    type Item = String;
    // ...
}
// impl ContainerA for Single { type Item = i32; ... }   // FEHLER: schon impl'd

Der zentrale Unterschied:

  • Generic-Parameter: ein Typ kann mehrere Implementations des Traits haben (eine pro Parameter-Belegung)
  • Associated Type: ein Typ kann nur eine Implementation haben (mit einem festgelegten Associated Type)

Daher gilt die Faustregel: Associated Type wenn pro Typ nur eine sinnvolle Implementation existiert, Generic-Parameter wenn mehrere sinnvoll sind.

Stdlib-Beispiele:

  • Iterator { type Item; } — ein Iterator hat einen Item-Typ, keine zwei.
  • From<T> for U — eine Konvertierung pro (Quell-Ziel)-Paar; passt zu Generic.

Mehrere Associated Types

Ein Trait kann beliebig viele Associated Types haben.

Rust Mehrere Types
trait Storage {
    type Key;
    type Value;
    type Error;

    fn get(&self, key: &Self::Key) -> Result<Self::Value, Self::Error>;
    fn put(&mut self, key: Self::Key, value: Self::Value) -> Result<(), Self::Error>;
}

struct MemoryStorage {
    data: std::collections::HashMap<String, Vec<u8>>,
}

impl Storage for MemoryStorage {
    type Key = String;
    type Value = Vec<u8>;
    type Error = String;

    fn get(&self, key: &String) -> Result<Vec<u8>, String> {
        self.data.get(key).cloned().ok_or_else(|| String::from("not found"))
    }

    fn put(&mut self, key: String, value: Vec<u8>) -> Result<(), String> {
        self.data.insert(key, value);
        Ok(())
    }
}

Drei Associated Types — Key, Value, Error — gemeinsam festgelegt. Andere Implementations könnten andere Typen wählen (MemoryStorage<u64, String> etc.).

Bounds an Associated Types

Du kannst Bounds direkt an Associated Types in Trait-Deklarationen setzen.

Rust Bound am Associated Type
use std::fmt::Display;

trait DisplayContainer {
    type Item: Display;          // Bound am Associated Type

    fn drucke_alle(&self);
}

struct StringList(Vec<String>);

impl DisplayContainer for StringList {
    type Item = String;          // String ist Display — OK

    fn drucke_alle(&self) {
        for s in &self.0 {
            println!("{s}");
        }
    }
}

type Item: Display; erzwingt: jede Implementation muss einen Display-fähigen Item-Typ wählen. Alternative wäre where Self::Item: Display in den Methoden, aber an der Trait-Deklaration ist es klarer.

Rust Mehrere Bounds
use std::hash::Hash;
use std::fmt::Debug;

trait Cache {
    type Key: Hash + Eq + Clone;
    type Value: Clone + Debug;

    fn lookup(&self, key: &Self::Key) -> Option<Self::Value>;
}

+-Kombination wie überall sonst in Bounds. Implementierungen müssen alle Bedingungen erfüllen.

Iterator<Item = T> in Bounds

Beim Verwenden eines Traits mit Associated Type kannst du den Type-Wert in der Bound festlegen.

Rust Item festlegen
// Akzeptiert NUR Iteratoren mit Item = i32
fn summe_i32<I: Iterator<Item = i32>>(iter: I) -> i32 {
    iter.sum()
}

// Akzeptiert NUR Iteratoren mit Item = String
fn drucke_strings<I: Iterator<Item = String>>(iter: I) {
    for s in iter {
        println!("{s}");
    }
}

// Generic über Item-Typ
fn count<I: Iterator>(iter: I) -> usize {
    iter.count()
}

Die Iterator<Item = i32>-Syntax sagt: jeder Typ I, der Iterator implementiert UND dessen Item gleich i32 ist. Das ist ein Equality-Constraint auf den Associated Type — Stdlib-Pattern, sehr verbreitet.

Rust Kombinierte Bounds
use std::fmt::Display;

// Iterator mit beliebigem Item, das aber Display sein muss
fn drucke<I>(iter: I)
where
    I: Iterator,
    I::Item: Display,        // Bound auf Associated Type
{
    for x in iter {
        println!("{x}");
    }
}

I::Item: Display ist ein Bound auf den Associated Type — funktioniert in where-Klauseln und ist die klare Variante, wenn du den Item-Typ nicht festlegst, aber Constraints stellst.

Default-Werte für Associated Types

Seit Rust 1.x (experimentell, in der Stdlib genutzt) können Associated Types Default-Werte haben — Stand 2026 stabil für einige Cases, instabil für andere.

Rust Mit Default (Add-Trait)
// Stdlib: std::ops::Add
// pub trait Add<Rhs = Self> {
//     type Output;
//     fn add(self, rhs: Rhs) -> Self::Output;
// }

// Default für Rhs ist Self → Add für selbst-gleich-typ Addition
// impl Add for i32 — Rhs default = i32
// impl Add<f64> for i32 — explizit andere Rhs

Im Trait-Deklaration ist Rhs = Self ein Generic-Parameter mit Default-Wert. Bei Associated Types ist die analoge Funktionalität teilweise via RFC stabilisiert; die häufigste Form bleibt: Generic-Parameter mit Default.

Praxis: Associated Types im echten Code

Iterator (Stdlib)

Rust Iterator
// Vereinfachte Stdlib-Definition:
pub trait MeinIterator {
    type Item;

    fn next(&mut self) -> Option<Self::Item>;
}

pub struct Counter { count: u32 }

impl MeinIterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option<u32> {
        if self.count < 5 {
            self.count += 1;
            Some(self.count)
        } else {
            None
        }
    }
}

fn main() {
    let mut c = Counter { count: 0 };
    while let Some(v) = c.next() {
        println!("{v}");
    }
}

Der Iterator-Trait ist das Paradebeispiel für Associated Types. Jeder Iterator hat genau einen Item-Typ — nicht mehrere.

Repository-Pattern

Rust Repository
pub trait Repository {
    type Entity;
    type Id;
    type Error;

    fn find(&self, id: Self::Id) -> Result<Self::Entity, Self::Error>;
    fn save(&mut self, entity: Self::Entity) -> Result<Self::Id, Self::Error>;
}

struct User { name: String }

struct UserRepo {
    users: Vec<User>,
}

impl Repository for UserRepo {
    type Entity = User;
    type Id = usize;
    type Error = String;

    fn find(&self, id: usize) -> Result<User, String> {
        self.users.get(id)
            .map(|u| User { name: u.name.clone() })
            .ok_or_else(|| format!("User {id} not found"))
    }

    fn save(&mut self, user: User) -> Result<usize, String> {
        self.users.push(user);
        Ok(self.users.len() - 1)
    }
}

Repository-Trait mit Entity, Id und Error als Associated Types. Pro Repository festgelegt — UserRepo hat keine zwei verschiedenen Entity-Typen.

Future-Trait (vereinfacht)

Rust Future-Pattern
// Stdlib: std::future::Future hat ähnliche Form
pub trait MyFuture {
    type Output;        // Was die Future am Ende liefert

    fn poll(&mut self) -> Option<Self::Output>;
}

struct DelayedValue<T> { value: Option<T> }

impl<T> MyFuture for DelayedValue<T> {
    type Output = T;

    fn poll(&mut self) -> Option<T> {
        self.value.take()
    }
}

Future hat einen Output-Typ als Associated Type — was beim Auflösen herauskommt.

Parser-Trait

Rust Parser
pub trait Parser {
    type Input;
    type Output;
    type Error;

    fn parse(&self, input: Self::Input) -> Result<Self::Output, Self::Error>;
}

struct JsonParser;
impl Parser for JsonParser {
    type Input = String;
    type Output = serde_json::Value;
    type Error = serde_json::Error;

    fn parse(&self, input: String) -> Result<serde_json::Value, serde_json::Error> {
        serde_json::from_str(&input)
    }
}

Parser mit Input/Output/Error als Associated Types. Jeder Parser-Typ legt seine drei Typen fest — pro Parser nur eine sinnvolle Belegung.

Service-Trait für DI

Rust Service
pub trait Service {
    type Request;
    type Response;

    fn call(&self, request: Self::Request) -> Self::Response;
}

struct EchoService;
impl Service for EchoService {
    type Request = String;
    type Response = String;

    fn call(&self, request: String) -> String {
        request
    }
}

// Verwender mit Bound-Konfiguration:
fn handle<S: Service<Request = String, Response = String>>(s: &S, msg: String) -> String {
    s.call(msg)
}

Service-Trait mit Request und Response als Associated Types. Konsumenten können entweder generisch (S: Service) oder mit Equality-Constraint (Service<Request = String, Response = String>) arbeiten.

Iterator-Adapter

Rust Custom-Adapter
pub struct Skip<I> {
    iter: I,
    n: usize,
}

impl<I: Iterator> Iterator for Skip<I> {
    type Item = I::Item;        // Item-Typ vom Wrapped-Iterator übernehmen

    fn next(&mut self) -> Option<I::Item> {
        while self.n > 0 {
            self.n -= 1;
            self.iter.next()?;
        }
        self.iter.next()
    }
}

Iterator-Adapter mit Item = I::Item — der Adapter übernimmt den Item-Typ des umschlossenen Iterators. Klassisches Stdlib-Pattern.

Bound-Kombination

Rust Item mit Bound
use std::fmt::Debug;

// Akzeptiert Iteratoren mit Debug-fähigem Item-Typ
pub fn drucke_debug<I>(iter: I)
where
    I: IntoIterator,
    I::Item: Debug,
{
    for x in iter {
        println!("{x:?}");
    }
}

fn main() {
    drucke_debug(vec![1, 2, 3]);
    drucke_debug(vec!["a", "b"]);
    drucke_debug([(1, "x"), (2, "y")]);
}

Bound an Associated Type über die where-Klausel. Die Funktion akzeptiert jeden IntoIterator, dessen Items Debug sind — sehr generisch.

Interessantes

Associated Type = pro Implementation EIN Typ.

type Item; im Trait-Body deklariert, type Item = String; in der Implementation festgelegt. In Signaturen via Self::Item referenziert.

Unterschied zu Generic-Parameter: Eindeutigkeit.

Generic-Parameter erlauben mehrere Implementationen pro Typ (impl Trait<i32> for Foo, impl Trait<String> for Foo). Associated Type erlaubt nur EINE Implementation pro Typ.

Faustregel: type wenn pro Typ eine sinnvolle Variante.

Iterator hat einen Item-Typ, kein zwei — Associated Type. From<T> kann pro Ziel-Typ mehrere Quell-Typen haben — Generic-Parameter. Stdlib-Konventionen folgen dieser Heuristik.

Iterator in Bounds — Equality-Constraint.

Beim Verwenden des Traits kannst du den Associated Type festlegen: I: Iterator<Item = i32> akzeptiert nur Iteratoren über i32. Sehr verbreitetes Stdlib-Pattern.

Bounds direkt am Associated Type — type Item: Display.

Im Trait-Body am Associated Type stellbar. Implementations müssen einen Display-fähigen Typ wählen. Alternative: where Self::Item: Display an den Methoden.

Mehrere Associated Types pro Trait — kein Problem.

type Key; type Value; type Error; zusammen deklariert, in der Implementation gemeinsam festgelegt. Pattern für Repository, Storage, Parser, Service.

Self::Type für Self-Referenzen.

Im Trait-Body und in Impl-Blöcken referenzierst du Associated Types als Self::Item. Außerhalb (in Bounds anderer Funktionen) als <T as Trait>::Item oder via Pfad-Syntax.

Associated Types machen APIs lesbarer.

Iterator::Item ist klarer als Iterator<Item> mit Generic-Parameter. Type-Inferenz funktioniert robuster, weil pro Typ nur ein Item-Wert möglich ist.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Traits

Zur Übersicht