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
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:
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:
# 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
| Name | Bedeutung | Beispiel |
|---|---|---|
new | Standard-Konstruktor | Vec::new() |
default | Default-Wert (oft via Default-Trait) | Vec::default() |
with_capacity(n) | Konstruktor mit Vor-Allokation | Vec::with_capacity(100) |
with_X | Konstruktor mit spezieller Konfiguration | String::with_capacity(n) |
from(value) | Konvertierung von einem anderen Typ | String::from("Hi") |
try_from(value) | Fallible Konvertierung | i32::try_from(100u64) |
parse | aus String konstruieren | i32::from_str("42") |
empty, null, zero | „leere" oder „null"-Variante | Vec3::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:
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:
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":
#[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:
# 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&strzuString."Hi".into()— generische Konvertierung überInto<String>.
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:
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
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
#[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
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
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
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
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
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
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
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
- The Rust Book – Associated Functions
- Rust Reference – Associated Items
- std::default::Default
- std::convert::From
- Rust API Guidelines – C-CTOR