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 — er folgt nur aus der Signatur:

  • Mit self-Parameter → Method → .-Aufruf an einer Instanz.
  • Ohne self-Parameter → Associated Function → ::-Aufruf am Typ.

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 konventionell, nicht reserviert. Du darfst sie nennen, wie du willst — aber new ist Standard und wird von API-Konsumenten erwartet.

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] }
    }
}

Klare Namen statt verwirrend überladen — typisch idiomatisch in Rust.

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()

Wer sich an diese Namen hält, schreibt APIs, die anderen Rust-Programmierern sofort vertraut sind.

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, &'static str> {
        let teile: Vec<&str> = s.split(',').collect();
        match teile.as_slice() {
            [eur, cent] => {
                let e: u64 = eur.parse().map_err(|_| "kein Euro")?;
                let c: u64 = cent.parse().map_err(|_| "kein Cent")?;
                Ok(e * 100 + c)
            }
            _ => Err("Format: EUR,CC"),
        }
    }
}

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

parse_cents ist eine Type-Level-Hilfs-Funktion — sie gehört thematisch zum Geld-Typ, baut aber keine Instanz. Solche Funktionen sind ideale Associated Functions.

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}");
}

Aufruf über ::-Syntax wie bei Associated Functions. Sehr nützlich für Domain-Konstanten, Default-Werte und magische Zahlen.

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 }
    }
}

Default::default() wird oft als Initial-Wert in Builder-Patterns oder bei Option::unwrap_or_default() genutzt.

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.
}

Mit From-Impl bekommst du Into kostenlos durch eine Blanket-Impl in der Stdlib. Das ist eines der idiomatischsten Patterns für Konvertierungs-Funktionen.

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);
}

Klare benannte Konstruktoren statt überladene new. Aufrufer sieht direkt, was er bekommt.

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 + Konstruktoren in einem impl-Block. Aufrufer kann beide Stile nutzen.

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,
        }
    }
}

new() als Default + with_capacity(n) für Konfiguration. Stdlib-Pattern (Vec, HashMap, String etc.).

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);
}

Mehrere From-Impls für verschiedene Eingabe-Typen. Aufrufer kann Tag::from(...) oder .into() nutzen.

TryFrom für validierende Konvertierung

Rust TryFrom
pub struct Port(u16);

impl TryFrom<u16> for Port {
    type Error = &'static str;
    fn try_from(value: u16) -> Result<Self, Self::Error> {
        if value < 1024 {
            Err("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;
}

TryFrom für fallible Konvertierung — Result zurück statt Direkt-Wert.

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.

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