Eine Associated Function ist eine Funktion in einem impl-Block, die keinen self-Receiver hat. Sie gehört zum Typ, nicht zu einer Instanz — du rufst sie mit ::-Syntax statt mit .-Notation auf: String::new(), Vec::with_capacity(100), Konto::neu(). Klassische Anwendung: Konstruktor-Funktionen, weil Rust keine speziellen Konstruktor-Syntax wie in Java oder C++ hat — jeder fn neu(...) -> Self ist einfach eine Associated Function. Dieser Artikel zeigt die Syntax, die Konventionen für Namen (new, from, with_, default) und die typischen Patterns.

Definition

Rust Assoziierte Funktion
struct Konto {
    saldo_cent: i64,
}

impl Konto {
    // Associated function — kein self
    fn neu() -> Self {
        Konto { saldo_cent: 0 }
    }

    // Method — &self
    fn saldo(&self) -> i64 {
        self.saldo_cent
    }
}

fn main() {
    let k = Konto::neu();         // ::-Syntax, weil kein self
    let s = k.saldo();             // .-Syntax, weil self vorhanden
    assert_eq!(s, 0);
}

Der Unterschied zwischen Konto::neu() und k.saldo() ist nicht semantisch tief, sondern folgt rein aus der Signatur. Eine Funktion mit self-Parameter (in irgendeiner Form: self, &self, &mut self) ist eine Method und wird per .-Notation an einer Instanz aufgerufen. Eine Funktion ohne self-Parameter ist eine Associated Function und wird per ::-Notation am Typ aufgerufen.

Diese Aufteilung ist syntaktisch sauber: Konto::neu() macht klar, dass hier keine Instanz nötig ist (logisch — sie soll ja erst erzeugt werden). k.saldo() macht klar, dass k die Instanz ist, an der die Methode arbeitet. In anderen Sprachen (Java, C++) sind Konstruktoren syntaktisch speziell (new Konto(), Konto()); Rust spart sich diese Sonderbehandlung und nutzt einfach den allgemeinen Mechanismus der Associated Function.

Konstruktor-Pattern: new

Rust hat kein Schlüsselwort für Konstruktoren. Stattdessen ist die Konvention: eine Associated Function namens new, die eine neue Instanz erzeugt:

Rust new()-Konvention
struct Vec3 { x: f64, y: f64, z: f64 }

impl Vec3 {
    pub fn new(x: f64, y: f64, z: f64) -> Self {
        Vec3 { x, y, z }
    }
}

fn main() {
    let v = Vec3::new(1.0, 2.0, 3.0);
}

new ist eine reine Konvention, kein reserviertes Schlüsselwort. Du könntest die Funktion auch erzeuge, make oder create nennen — der Compiler interessiert sich nicht für den Namen. Aber: Konsumenten deiner Library erwarten new, weil es überall in der Stdlib und in idiomatischem Rust-Code so heißt. Vec::new(), String::new(), HashMap::new(), BTreeMap::new() — wer das Pattern kennt, sucht bei deinem Struct als erstes nach MeinStruct::new().

In dieser Doku verwenden wir teilweise neu als deutsche Alternative für Lehrbeispiele. Im produktiven Code solltest du der englischsprachigen Konvention folgen — das macht deine API für die internationale Rust-Community sofort lesbar.

Mehrere Konstruktoren

Da Rust kein Overloading hat, brauchst du mehrere Funktionen mit verschiedenen Namen:

Rust Mehrere Konstruktoren
# struct Vec3 { x: f64, y: f64, z: f64 }
impl Vec3 {
    pub fn new(x: f64, y: f64, z: f64) -> Self {
        Vec3 { x, y, z }
    }

    pub fn null() -> Self {
        Vec3 { x: 0.0, y: 0.0, z: 0.0 }
    }

    pub fn von_array(a: [f64; 3]) -> Self {
        Vec3 { x: a[0], y: a[1], z: a[2] }
    }
}

Rust hat kein Function-Overloading — du kannst nicht zwei Funktionen new(x, y, z) und new(arr) parallel definieren, die sich nur in der Signatur unterscheiden. Stattdessen bekommen die verschiedenen Konstruktoren eindeutige, beschreibende Namen: new für die Standard-Form, null für den Null-Vektor, von_array für die Konvertierung aus einem Array.

Diese Variante ist verbose, hat aber Vorteile: aus dem Aufrufer-Code geht direkt hervor, welche Konstruktor-Variante gewählt wurde. Vec3::null() ist klarer als Vec3::new(0.0, 0.0, 0.0) und auch klarer als ein hypothetisches überladenes Vec3::new(). Konventionelle Namen wie new, default, with_capacity, from, try_from, with_X helfen, dass deine API für Rust-Entwickler intuitiv ist.

Naming-Konventionen

NameBedeutungBeispiel
newStandard-KonstruktorVec::new()
defaultDefault-Wert (oft via Default-Trait)Vec::default()
with_capacity(n)Konstruktor mit Vor-AllokationVec::with_capacity(100)
with_XKonstruktor mit spezieller KonfigurationString::with_capacity(n)
from(value)Konvertierung von einem anderen TypString::from("Hi")
try_from(value)Fallible Konvertierungi32::try_from(100u64)
parseaus String konstruiereni32::from_str("42")
empty, null, zero„leere" oder „null"-VarianteVec3::null()

Diese Namens-Konventionen sind nicht nur Stil-Empfehlungen — sie sind die Grundlage für idiomatic Rust. Wer eine API entwirft und sich daran hält, schreibt Code, der sich für Rust-Programmierer sofort vertraut anfühlt. Wer eigene Namen wählt (z. B. MyStruct::create() statt new(), oder convert() statt from()), zwingt Konsumenten zu permanenter Dokumentations-Lektüre.

Besonders wichtig ist die from/try_from-Konvention: sie verbindet sich mit den Stdlib-Traits From und TryFrom, und das into()-Methode ergibt sich automatisch über die Blanket-Impls. Wenn du From<X> for Y implementierst, bekommen alle Konsumenten gratis x.into() und können in generischen impl Into<Y>-Parametern arbeiten.

Associated Functions ohne Self-Return

Nicht jede Associated Function muss eine Instanz erzeugen. Sie kann auch Hilfs-Funktionen sein, die zum Typ thematisch gehören:

Rust Utility-Funktion
struct Geld { eur: u32, cent: u8 }

impl Geld {
    pub fn neu(eur: u32, cent: u8) -> Self {
        Geld { eur, cent: cent.min(99) }
    }

    // Type-Level-Utility — keine Instanz, keine Rückgabe von Self
    pub fn parse_cents(s: &str) -> Result<u64, String> {
        let teile: Vec<&str> = s.split(',').collect();
        match teile.as_slice() {
            [eur, cent] => {
                let e: u64 = eur.parse().map_err(|_| String::from("kein Euro"))?;
                let c: u64 = cent.parse().map_err(|_| String::from("kein Cent"))?;
                Ok(e * 100 + c)
            }
            _ => Err(String::from("Format: EUR,CC")),
        }
    }
}

fn main() {
    let cents = Geld::parse_cents("19,90").unwrap();
    assert_eq!(cents, 1990);
}

parse_cents ist eine Type-Level-Hilfsfunktion: sie gehört thematisch zum Geld-Typ (sie versteht das Geld-Format), baut aber keine Geld-Instanz, sondern liefert nur den Cents-Wert. Solche Funktionen sind ideale Associated Functions — der Aufrufer ruft Geld::parse_cents("..."), und die Namensgebung macht klar, in welchem semantischen Kontext die Funktion arbeitet.

Die Alternative wäre eine freie Funktion irgendwo im Modul (fn parse_geld_cents(...)). Die Associated-Function-Variante ist organisatorisch besser: alle Geld-bezogenen Funktionen liegen am Typ und sind über die Geld::-Namensraum auffindbar. Bei IDE-Auto-Completion (etwa Geld:: + Tab) bekommst du alle relevanten Funktionen direkt vorgeschlagen.

Associated Constants

Associated Constants sind verwandt: Werte, die zum Typ gehören:

Rust Konstanten
struct Kreis;

impl Kreis {
    pub const PI: f64 = 3.141592653589793;
    pub const KONST_EULER: f64 = 0.5772156649;
}

fn main() {
    let umfang = 2.0 * Kreis::PI * 5.0;
    println!("{umfang}");
}

Associated Constants sind das ruhende Geschwister der Associated Functions: Werte (statt Funktionen), die zum Typ gehören. Aufruf ebenfalls per ::-Syntax — Kreis::PI. Sie sind sehr nützlich für Domain-Konstanten (wie mathematische Konstanten), Default-Werte (Vec3::NULL), oder magische Zahlen, die zum Typ logisch dazugehören.

Im Vergleich zu globalen const-Items in einem Modul haben Associated Constants den Vorteil der Namensraum-Sortierung: alles Kreis-Bezogene findet sich unter Kreis::*. Aus IDE-Perspektive ist das auch besser auffindbar.

Trait-basierter Konstruktor: Default

Der Default-Trait standardisiert „Default-Konstruktor":

Rust Default-Trait
#[derive(Default)]
struct Konto {
    saldo_cent: i64,
    // Alle Felder müssen Default haben — i64::default() = 0
}

fn main() {
    let k = Konto::default();    // saldo_cent = 0
    assert_eq!(k.saldo_cent, 0);
}

#[derive(Default)] generiert Konto::default(). Voraussetzung: alle Felder implementieren Default. Für eigene Default-Werte: manuelle Implementierung:

Rust Custom Default
# struct Konto { saldo_cent: i64 }
impl Default for Konto {
    fn default() -> Self {
        Konto { saldo_cent: 0 }
    }
}

#[derive(Default)] ist der schnellste Weg zu einem Default-Konstruktor — der Compiler generiert die Default-Implementation automatisch, sofern alle Felder selbst Default implementieren. Bei Standard-Typen wie i64, String, Vec<T>, HashMap ist das automatisch erfüllt.

Wenn du nicht-triviale Defaults brauchst (z. B. eine Konfiguration mit Default-URL), implementierst du Default manuell. Die Methode wird dann genauso aufgerufen — Konfig::default() —, aber sie kann beliebige Initialisierungs-Logik enthalten. Der Default::default()-Aufruf taucht überall auf: als Initial-Wert in Builder-Patterns, in generischem Code (T::default()), bei Option::unwrap_or_default() und Result::unwrap_or_default().

From / TryFrom — Konvertierungs-Konstruktoren

From<T> und TryFrom<T> sind Traits für Konvertierungen. Wer sie implementiert, bekommt automatisch:

  • String::from("Hi") — Konvertierung von &str zu String.
  • "Hi".into() — generische Konvertierung über Into<String>.
Rust From-Impl
struct UserId(u64);

impl From<u64> for UserId {
    fn from(value: u64) -> Self {
        UserId(value)
    }
}

fn main() {
    let id1 = UserId::from(42);
    let id2: UserId = 42.into();
    // Beide gleichwertig.
}

Die From/Into-Mechanik ist ein zentrales Idiom in Rust. Du implementierst From<X> for Y, und automatisch hast du Into<Y> for X — über eine Blanket-Impl in der Stdlib. Dadurch kann jeder Aufrufer wählen, ob er Y::from(x) (explizit am Ziel-Typ) oder x.into() (im Kontext) verwendet.

Die into()-Variante ist besonders mächtig in generischen Funktions-Parametern: fn foo(x: impl Into<UserId>) akzeptiert sowohl u64-Werte (Konvertierung über From<u64>) als auch direkte UserId-Instanzen. Damit baust du flexible APIs, ohne separate Overloads schreiben zu müssen. Ein einzelner From-Impl gibt dir dieses ganze Flexibilitäts-Spektrum kostenlos.

Aufruf via Type-Alias

Associated Functions funktionieren auch mit Type-Aliasen:

Rust Alias
type UserMap = std::collections::HashMap<u64, String>;

fn main() {
    let map: UserMap = UserMap::new();      // via Alias
}

Sehr nützlich, wenn der Original-Typ generisch ist und der Alias spezialisiert.

Praxis: Associated Functions im echten Code

Mehrere Konstruktor-Varianten

Rust Multi-Constructor
pub struct Server {
    host: String,
    port: u16,
    tls: bool,
}

impl Server {
    pub fn new(host: impl Into<String>, port: u16) -> Self {
        Server { host: host.into(), port, tls: false }
    }

    pub fn neu_mit_tls(host: impl Into<String>, port: u16) -> Self {
        Server { host: host.into(), port, tls: true }
    }

    pub fn localhost(port: u16) -> Self {
        Server::new("localhost", port)
    }
}

fn main() {
    let a = Server::new("example.com", 80);
    let b = Server::neu_mit_tls("example.com", 443);
    let c = Server::localhost(8080);
    let _ = (a.host, b.host, c.host);
}

Wenn ein Typ verschiedene Konstruktions-Modi anbietet, sollten sie klar benannt sein. Server::new(host, port) für die generische Form, Server::neu_mit_tls(host, port) mit explizitem TLS-Hinweis, Server::localhost(port) als Convenience für die häufige Test-/Dev-Konfiguration. Jeder Konstruktor sagt im Namen, was er macht.

Diese Variante ist ergonomischer als ein generisches new(host, port, options) mit Options-Struct: der Aufrufer muss keine Default-Werte für irrelevante Parameter setzen, und die API ist selbsterklärend. Bei sehr vielen Konstruktoren mit komplexen Konfigurationen kippt das Verhältnis irgendwann zugunsten eines Builder-Patterns — das eigene Kapitel dazu folgt.

Konstanten + Konstruktor zusammen

Rust Vec2D
#[derive(Clone, Copy)]
pub struct Vec2 { pub x: f64, pub y: f64 }

impl Vec2 {
    pub const NULL: Vec2 = Vec2 { x: 0.0, y: 0.0 };
    pub const EINHEIT_X: Vec2 = Vec2 { x: 1.0, y: 0.0 };
    pub const EINHEIT_Y: Vec2 = Vec2 { x: 0.0, y: 1.0 };

    pub fn new(x: f64, y: f64) -> Self { Vec2 { x, y } }
    pub fn rotation(winkel: f64) -> Self {
        Vec2 { x: winkel.cos(), y: winkel.sin() }
    }
}

fn main() {
    let o = Vec2::NULL;
    let x = Vec2::EINHEIT_X;
    let r = Vec2::rotation(std::f64::consts::PI / 4.0);
    let _ = (o.x, x.x, r.x);
}

Konstanten und Konstruktoren passen wunderbar zusammen in einem impl-Block. Die häufig genutzten Standardwerte (NULL, EINHEIT_X, EINHEIT_Y) sind als const direkt am Typ verfügbar — keine Allokation, kein Funktionsaufruf, nur ein Typ-Member-Access. Für berechnete Werte gibt es die Konstruktor-Variante (new, rotation).

Der Aufrufer kann je nach Situation wählen: Vec2::NULL ist klarer und schneller als Vec2::new(0.0, 0.0). Vec2::rotation(winkel) hingegen kann nicht als Konstante existieren, weil der Wert vom Argument abhängt.

Cache-Builder

Rust Cache mit Capacity-Konstruktor
use std::collections::HashMap;

pub struct LruCache {
    map: HashMap<String, String>,
    kapazitaet: usize,
}

impl LruCache {
    pub fn new() -> Self {
        LruCache::with_capacity(100)
    }

    pub fn with_capacity(kapazitaet: usize) -> Self {
        LruCache {
            map: HashMap::with_capacity(kapazitaet),
            kapazitaet,
        }
    }
}

Das new()/with_capacity()-Doppelpattern ist eines der häufigsten in der Stdlib — Vec, HashMap, String, VecDeque, HashSet alle bieten es. new() ist der minimale Default, with_capacity(n) ist die optimierte Variante für bekannte Größen.

Im Beispiel wird new() als Convenience-Wrapper über with_capacity(100) implementiert — eine schöne Form, Code-Duplikation zu vermeiden. Wenn du an dem 100-Default später etwas ändern willst, musst du es nur einmal anpassen. Diese Komposition kleiner Konstruktoren ist typisches gutes API-Design.

Conversion-Konstruktoren

Rust From-Impls
pub struct Tag(String);

impl Tag {
    pub fn new(name: impl Into<String>) -> Self {
        Tag(name.into())
    }
}

impl From<&str> for Tag {
    fn from(s: &str) -> Self {
        Tag(s.to_string())
    }
}

impl From<String> for Tag {
    fn from(s: String) -> Self {
        Tag(s)
    }
}

fn main() {
    let t1 = Tag::from("rust");
    let t2 = Tag::from(String::from("tutorial"));
    let t3: Tag = "ownership".into();
    let _ = (t1.0, t2.0, t3.0);
}

Wenn dein Typ aus mehreren Quell-Typen konstruierbar sein soll, implementierst du From<X> für jeden davon. Hier wird From<&str> und From<String> für Tag implementiert — beide allokieren intern, aber der Aufrufer kann je nach Datenquelle die passende Form nutzen, ohne Konvertierung im Voraus.

Die .into()-Variante macht das in generischem Code besonders bequem: eine Funktion mit tag: impl Into<Tag>-Parameter akzeptiert &str, String, Tag direkt — alle drei werden automatisch konvertiert. Das macht Library-APIs sehr nutzerfreundlich, ohne dass du selbst Overloads schreiben musst.

TryFrom für validierende Konvertierung

Rust TryFrom
pub struct Port(u16);

impl TryFrom<u16> for Port {
    type Error = String;
    fn try_from(value: u16) -> Result<Self, Self::Error> {
        if value < 1024 {
            Err(String::from("Port < 1024 ist privilegiert"))
        } else {
            Ok(Port(value))
        }
    }
}

fn main() {
    let p1 = Port::try_from(8080).unwrap();
    let p2 = Port::try_from(80);
    assert!(p2.is_err());
    let _ = p1.0;
}

Wenn die Konvertierung scheitern kann (etwa weil die Eingabe Validierungs-Bedingungen verletzt), nutzt du TryFrom statt From. Die Methode try_from gibt Result<Self, Error> zurück — Erfolg oder ein Fehler-Wert.

Im Beispiel validiert Port::try_from den Wertebereich: Ports unter 1024 sind auf Unix-Systemen privilegiert und brauchen Root-Rechte, also lehnen wir sie ab. Damit hast du eine Typ-Garantie: jede Port-Instanz, die existiert, ist garantiert ein nicht-privilegierter Port. Andere Funktionen können sich darauf verlassen, ohne selbst zu prüfen — das ist wieder das „Make Invalid States Unrepresentable"-Prinzip.

Static-Singleton-Pattern

Rust Logger-Singleton
use std::sync::OnceLock;

pub struct Logger {
    level: String,
}

impl Logger {
    pub fn instanz() -> &'static Logger {
        static LOGGER: OnceLock<Logger> = OnceLock::new();
        LOGGER.get_or_init(|| Logger { level: "INFO".into() })
    }

    pub fn log(&self, msg: &str) {
        println!("[{}] {msg}", self.level);
    }
}

fn main() {
    Logger::instanz().log("Start");
    Logger::instanz().log("Verarbeite");
}

instanz() als Associated Function plus OnceLock für lazy-initialisierten Singleton. Der Rückgabetyp &'static Logger ist eine Referenz, die für die gesamte Programm-Laufzeit gültig bleibt — die Markierung 'static ist eine Lifetime und wird in Kapitel 16 ausführlich behandelt.

Builder-Konstruktor

Rust Builder-Init
pub struct Anfrage {
    url: String,
    timeout_ms: u32,
}

impl Anfrage {
    pub fn an(url: impl Into<String>) -> Self {
        Anfrage { url: url.into(), timeout_ms: 5000 }
    }

    pub fn timeout(mut self, ms: u32) -> Self {
        self.timeout_ms = ms;
        self
    }
}

fn main() {
    let req = Anfrage::an("https://example.com").timeout(10_000);
    let _ = req.url;
}

an() als sprechender Konstruktor — semantisch klarer als new(...).

Helper-Funktion am Typ

Rust String-Utility
pub struct Slug;

impl Slug {
    pub fn aus(text: &str) -> String {
        text.chars()
            .filter(|c| c.is_alphanumeric() || c.is_whitespace())
            .map(|c| if c.is_whitespace() { '-' } else { c.to_ascii_lowercase() })
            .collect()
    }
}

fn main() {
    assert_eq!(Slug::aus("Hello World!"), "hello-world");
}

Slug als Unit-Struct dient als „Namespace" für die aus-Funktion. Klassisches Pattern für Helper-Funktionen, die thematisch zu einem Konzept gehören.

Konstante mit Konstruktor-Helfer

Rust Konfig-Variants
pub struct Theme {
    pub hintergrund: String,
    pub text: String,
}

impl Theme {
    pub const DARK: fn() -> Theme = || Theme {
        hintergrund: "#1a1a1a".into(),
        text: "#e0e0e0".into(),
    };

    pub fn hell() -> Self {
        Theme {
            hintergrund: "#ffffff".into(),
            text: "#202020".into(),
        }
    }
}

fn main() {
    let h = Theme::hell();
    let d = (Theme::DARK)();
    let _ = (h.text, d.text);
}

FAQ

Was unterscheidet Method von Associated Function?

Method hat self-Receiver (&self, &mut self, self) — Aufruf mit .-Syntax. Associated Function hat keinen self — Aufruf mit ::-Syntax. Beide leben im gleichen impl-Block.

Gibt es Konstruktoren wie in Java?

Nein, kein Schlüsselwort. Stattdessen ist die Konvention: eine Associated Function namens new, die Self zurückgibt. Du darfst sie auch anders nennen — der Compiler erzwingt nichts.

Warum new statt eines speziellen Schlüsselworts?

Weil Konstruktoren in Rust gewöhnliche Funktionen sind — sie können fehlschlagen (Rückgabe Result), mehrere Varianten haben, generisch sein, andere Konstruktoren aufrufen. Spezielle Konstruktor-Syntax in OOP-Sprachen schränkt diese Flexibilität ein.

Self oder konkreten Typ?

Innerhalb impl T { ... } sind Self und T austauschbar. Self ist idiomatischer — sieht in generischen Implementierungen besser aus, hält den Code bei Typ-Umbenennungen stabil. fn new() -> Self ist Standard.

Wann new, wann default?

new() für Konstruktion mit Parameter-Argumenten oder spezifischer Logik. default() für „leeren Standard"-Wert, der per Default-Trait standardisiert ist. Viele Typen haben beide: Vec::new() (Default-Konstruktor) und Vec::default() (über Default-Trait, gleichbedeutend).

From impliziert Into.

Wenn du impl From<u64> for UserId schreibst, bekommst du Into<UserId> für u64 kostenlos durch eine Blanket-Impl in der Stdlib. Damit ist 42.into() (mit let x: UserId = ...) gleichwertig zu UserId::from(42).

Associated Constants sind const-Items im impl.

impl T { pub const VAL: i32 = 42; } definiert eine Konstante am Typ. Aufruf: T::VAL. Sehr nützlich für Domain-Konstanten wie Vec3::NULL, Color::RED. Können auch via Trait T: SomeTrait aus dem Trait kommen.

Aufruf mit Fully-Qualified-Syntax.

T::method(...) ruft eine Associated Function. Bei Mehrdeutigkeit (z. B. mehrere Traits mit gleichem Namen): <T as Trait>::method(...). Diese „Fully Qualified Syntax" ist selten nötig, hilft aber bei Ambiguitäten.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Structs & Methoden

Zur Übersicht