Field-Structs sind die Default-Form für eigene Datentypen in Rust. Sie kombinieren mehrere Werte mit unterschiedlichen Typen zu einer benannten Einheit, jedes Feld bekommt einen sprechenden Namen, und die ganze Struktur lässt sich wie ein eigener Typ behandeln. Dieser Artikel zeigt die Syntax für Deklaration und Konstruktion, das Field-Init-Shorthand, die Update-Syntax mit ..-Operator für Teil-Kopien, Pattern-Destrukturierung mit let/match, Sichtbarkeit auf Feld-Ebene und das Memory-Layout, das aus der Deklaration entsteht.

Deklaration

Rust Field-Struct
struct Person {
    name: String,
    alter: u32,
    ist_aktiv: bool,
}

Syntax:

  • struct als Schlüsselwort.
  • Name in UpperCamelCase (Konvention).
  • Felder im Block, jeweils name: Typ, kommasepariert. Trailing-Komma erlaubt.

Jedes Feld hat einen Typ — keine Field-Type-Inference. Die Felder können von beliebigem Typ sein, einschließlich anderer Structs, Referenzen (mit Lifetime), generischer Type-Parameter.

Konstruktion

Rust Instanz erstellen
fn main() {
    let p = Person {
        name: String::from("Anna"),
        alter: 28,
        ist_aktiv: true,
    };
    println!("{} ist {} Jahre alt", p.name, p.alter);
}
# struct Person { name: String, alter: u32, ist_aktiv: bool }

Die Konstruktion mit Name { feld: wert, ... } ist die Standardform — auch struct literal expression genannt. Reihenfolge der Felder ist frei (Compiler matched per Name), aber alle Felder müssen angegeben werden.

Field-Init-Shorthand

Wenn der lokale Variablenname mit dem Feldnamen übereinstimmt, kannst du das Wiederholen vermeiden:

Rust Shorthand
fn baue_person(name: String, alter: u32) -> Person {
    // statt: Person { name: name, alter: alter, ist_aktiv: true }
    Person {
        name,
        alter,
        ist_aktiv: true,
    }
}
# struct Person { name: String, alter: u32, ist_aktiv: bool }

Sehr typisch in Konstruktor-Funktionen und Setter-Pattern.

Update-Syntax mit ..

Wenn du eine Instanz mit nur wenigen Änderungen zu einer bestehenden erstellen willst, gibt es die ..-Update-Syntax:

Rust Update
let original = Person {
    name: String::from("Anna"),
    alter: 28,
    ist_aktiv: true,
};

let inaktiv = Person {
    ist_aktiv: false,
    ..original
};
// inaktiv hat name="Anna" und alter=28 (von original),
// ist_aktiv=false (explizit gesetzt).
# struct Person { name: String, alter: u32, ist_aktiv: bool }

Wichtig:

  • ..original muss als letztes stehen.
  • Felder, die du explizit setzt, kommen aus dem Literal.
  • Andere Felder werden aus original gemoved (oder bei Copy-Feldern: kopiert). Nach dem Update ist original ggf. nicht mehr vollständig nutzbar.
Rust Move-Verhalten
let original = Person {
    name: String::from("Anna"),       // String — Move!
    alter: 28,                         // u32 — Copy
    ist_aktiv: true,                   // bool — Copy
};

let inaktiv = Person {
    name: String::from("Bert"),        // explizit gesetzt
    ..original
};
// original.alter und original.ist_aktiv noch nutzbar (Copy).
// original.name nicht — kein Move, da explizit überschrieben.
// original als Ganzes nicht mehr nutzbar (partial move).
# struct Person { name: String, alter: u32, ist_aktiv: bool }

Bei Copy-Feldern keine Probleme. Bei String/Vec partielle Moves — siehe Move-Semantik-Artikel.

Field-Access

Rust Lesen und Schreiben
let mut p = Person {
    name: String::from("Anna"),
    alter: 28,
    ist_aktiv: true,
};

// Lesen
println!("{} {}", p.name, p.alter);

// Schreiben (Struct-Bindung muss mut sein!)
p.alter = 29;
p.name.push_str(" Müller");
# struct Person { name: String, alter: u32, ist_aktiv: bool }

Wichtig: für Schreibzugriff muss die gesamte Struct-Bindung mut sein — Rust hat keine Per-Field-Mutability auf Bindungs-Ebene. Wer feinere Kontrolle braucht: Cell / RefCell für Interior Mutability (siehe Smart-Pointer-Kapitel).

Destrukturierung mit Patterns

Wie schon im let-Artikel: Structs lassen sich mit Patterns destrukturieren:

Rust Pattern-Destrukturierung
let p = Person { name: String::from("Anna"), alter: 28, ist_aktiv: true };

let Person { name, alter, ist_aktiv } = p;
println!("{name} {alter} {ist_aktiv}");

// Selektiv:
let Person { name, .. } = Person {
    name: String::from("Bert"), alter: 30, ist_aktiv: false,
};

// Umbenennen:
let Person { name: vorname, alter: jahre, .. } = Person {
    name: String::from("Clara"), alter: 41, ist_aktiv: true,
};
# struct Person { name: String, alter: u32, ist_aktiv: bool }

Sehr nützlich bei Funktions-Parametern und in match-Armen.

Patterns in Funktions-Parametern

Rust Direkt destrukturieren
struct Punkt { x: f64, y: f64 }

fn distanz(Punkt { x: x1, y: y1 }: Punkt, Punkt { x: x2, y: y2 }: Punkt) -> f64 {
    ((x2 - x1).powi(2) + (y2 - y1).powi(2)).sqrt()
}

Sichtbarkeit pro Feld

Felder können einzeln pub markiert werden:

Rust Field-Visibility
pub struct User {
    pub username: String,           // öffentlich
    pub email: String,
    password_hash: String,           // privat — nur innerhalb des Moduls
    created_at: u64,                 // privat
}

Anwendung außerhalb des Moduls:

Rust Aus anderem Modul
// user.username = "...";        // ok — pub
// user.email = "...";           // ok — pub
// user.password_hash = "...";   // Fehler — privat

Das ermöglicht klassisches Encapsulation: nur ausgewählte Felder sind Teil des öffentlichen API, der Rest bleibt Modul-intern.

Struct ohne pub-Felder — Black Box

Rust Komplett private Felder
pub struct AppConfig {
    host: String,
    port: u16,
}

impl AppConfig {
    pub fn neu(host: String, port: u16) -> Self {
        AppConfig { host, port }
    }
    pub fn host(&self) -> &str { &self.host }
    pub fn port(&self) -> u16 { self.port }
}

Außerhalb des Moduls ist AppConfig nur über Methoden zugänglich — komplette Kapselung. Klassische API-Design-Form für Library-Crates.

Memory-Layout

Ein Struct belegt im Speicher genau die Summe seiner Felder plus eventuelles Padding für Alignment:

Rust size_of
use std::mem::size_of;

struct A { x: u8, y: u8 }
struct B { x: u8, y: u64 }
struct C { x: u64, y: u8 }

fn main() {
    println!("{}", size_of::<A>());     // 2
    println!("{}", size_of::<B>());     // 16 (Padding!)
    println!("{}", size_of::<C>());     // 16 (Padding am Ende)
}

Bei B { x: u8, y: u64 }:

  • x belegt 1 Byte an Offset 0.
  • y muss auf 8-Byte-Grenze liegen → 7 Bytes Padding nach x.
  • y belegt 8 Bytes an Offset 8.
  • Gesamt: 16 Bytes.

Reihenfolge der Felder

Der Compiler darf in Rust standardmäßig Felder umordnen, um Padding zu minimieren. Das heißt: die Speicher-Reihenfolge muss nicht der Quellcode-Reihenfolge entsprechen.

Wer garantierte Reihenfolge braucht (z. B. für FFI mit C-Bindings):

Rust Garantiertes Layout
#[repr(C)]
struct CKompatibel {
    x: u8,
    y: u64,
}

#[repr(C)] erzwingt C-kompatibles Layout (Felder in Deklarations-Reihenfolge, C-Padding-Regeln). Pflicht für FFI-Strukturen.

Praxis: Field-Structs im echten Code

User-Profil

Rust User
pub struct User {
    pub id: u64,
    pub username: String,
    pub email: String,
    pub erstellt_am: u64,
    email_verifiziert: bool,
    // Privat — Repräsentation soll später wechseln können
}

impl User {
    pub fn neu(id: u64, username: String, email: String) -> Self {
        User {
            id,
            username,
            email,
            erstellt_am: heute(),
            email_verifiziert: false,
        }
    }

    pub fn ist_verifiziert(&self) -> bool {
        self.email_verifiziert
    }
}

fn heute() -> u64 { 0 }

Public-Felder für die externe API, privates Feld für interne Logik. Konstruktor neu als Associated Function.

2D-Vektor mit Update-Syntax

Rust 2D-Vector
#[derive(Debug, 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 fn neu(x: f64, y: f64) -> Self { Vec2 { x, y } }
    pub fn laenge(&self) -> f64 { (self.x * self.x + self.y * self.y).sqrt() }
}

fn main() {
    let p = Vec2::neu(3.0, 4.0);
    // Update-Syntax — nur y überschreiben
    let p_horizontal = Vec2 { y: 0.0, ..p };
    assert_eq!(p_horizontal.x, 3.0);
}

Copy-Felder erlauben Update-Syntax ohne Move-Probleme.

HTTP-Request

Rust HTTP-Daten
pub struct HttpRequest {
    pub method: String,
    pub pfad: String,
    pub headers: Vec<(String, String)>,
    pub body: Vec<u8>,
}

impl HttpRequest {
    pub fn get(pfad: impl Into<String>) -> Self {
        HttpRequest {
            method: "GET".into(),
            pfad: pfad.into(),
            headers: Vec::new(),
            body: Vec::new(),
        }
    }

    pub fn header_hinzufuegen(&mut self, name: &str, wert: &str) {
        self.headers.push((name.to_string(), wert.to_string()));
    }
}

fn main() {
    let mut req = HttpRequest::get("/users/42");
    req.header_hinzufuegen("Accept", "application/json");
}

Mehrere Felder unterschiedlicher Typen, klar benannt, mit Domain-passenden Konstruktoren.

Config mit Pattern-Destrukturierung

Rust Config-Verarbeitung
struct Config {
    host: String,
    port: u16,
    workers: u32,
    log_level: String,
}

fn validiere(config: &Config) -> Result<(), &'static str> {
    let Config { host, port, workers, .. } = config;
    if host.is_empty() { return Err("Host darf nicht leer sein"); }
    if *port == 0 { return Err("Port 0 ist ungültig"); }
    if *workers == 0 { return Err("Workers muss > 0 sein"); }
    Ok(())
}

Destrukturierung als Lese-Optimierung — die Felder sind im Funktions-Scope direkt verfügbar.

State-Container

Rust App-State
pub struct AppState {
    pub aktive_users: u32,
    pub fehler_zaehler: u32,
    pub gesamt_anfragen: u64,
}

impl AppState {
    pub fn neu() -> Self {
        AppState {
            aktive_users: 0,
            fehler_zaehler: 0,
            gesamt_anfragen: 0,
        }
    }

    pub fn anfrage_zaehlen(&mut self, war_fehler: bool) {
        self.gesamt_anfragen += 1;
        if war_fehler { self.fehler_zaehler += 1; }
    }
}

Globale Statistik-Struktur mit Mutator-Methoden — typisch für Monitoring und Server-Stats.

Nested Structs

Rust Verschachtelt
pub struct Adresse {
    pub strasse: String,
    pub plz: String,
    pub stadt: String,
}

pub struct Kunde {
    pub id: u64,
    pub name: String,
    pub rechnungs_adresse: Adresse,
    pub liefer_adresse: Option<Adresse>,
}

fn main() {
    let k = Kunde {
        id: 1,
        name: "Anna".into(),
        rechnungs_adresse: Adresse {
            strasse: "Hauptstr. 1".into(),
            plz: "10115".into(),
            stadt: "Berlin".into(),
        },
        liefer_adresse: None,
    };
    println!("{}", k.rechnungs_adresse.stadt);
}

Felder können andere Structs enthalten — beliebig verschachtelt. Option<Adresse> für optionale Liefer-Adresse.

Generische Struct mit Type-Parametern

Rust Generisch
pub struct Paginated<T> {
    pub items: Vec<T>,
    pub gesamt: u64,
    pub seite: u32,
    pub per_seite: u32,
}

impl<T> Paginated<T> {
    pub fn ist_letzte_seite(&self) -> bool {
        (self.seite as u64 * self.per_seite as u64) >= self.gesamt
    }
}

fn main() {
    let p: Paginated<String> = Paginated {
        items: vec!["a".into(), "b".into()],
        gesamt: 100,
        seite: 1,
        per_seite: 20,
    };
    println!("Letzte? {}", p.ist_letzte_seite());
}

Type-Parameter <T> machen den Struct für beliebige Element-Typen verwendbar.

Struct mit Referenz-Feld und Lifetime

Rust Zero-Copy-View
pub struct TextSegment<'a> {
    pub text: &'a str,
    pub start: usize,
    pub end: usize,
}

impl<'a> TextSegment<'a> {
    pub fn neu(text: &'a str, start: usize, end: usize) -> Self {
        TextSegment { text, start, end }
    }

    pub fn als_str(&self) -> &str {
        &self.text[self.start..self.end]
    }
}

'a-Lifetime garantiert: TextSegment lebt nicht länger als der Original-Text. Klassisch für Parser und View-Strukturen.

Interessantes

Field-Init-Shorthand spart Wiederholung.

Person { name, alter } statt Person { name: name, alter: alter }. Funktioniert, wenn lokaler Variablenname mit Feldnamen identisch ist — sehr typisch in Konstruktoren.

Update-Syntax (..) ist Move-aware.

Bei Person { name, ..original } werden alle Felder außer name aus original gemoved. Felder mit Copy-Typ werden kopiert. original ist danach evtl. als Ganzes nicht mehr nutzbar (partial move), einzelne unverbrauchte Felder schon.

Reihenfolge der Felder im Speicher ist nicht garantiert.

Der Compiler darf umordnen, um Padding zu minimieren. Für FFI oder definierte Memory-Layouts: #[repr(C)] für C-Kompatibilität oder #[repr(packed)] für minimales Padding (Vorsicht: Alignment-Probleme).

Sichtbarkeit gilt pro Feld.

Du kannst einen pub struct haben, dessen Felder teilweise privat sind. Konsumenten außerhalb des Moduls sehen nur die pub-Felder. Klassisches Pattern für Capsule-Design.

Pattern-Destrukturierung mit .. ignoriert Rest.

let Person { name, .. } = p; bindet nur name. Die anderen Felder werden ignoriert — aber Drop-Verhalten des Originals bleibt unverändert. Im match: _ für einzelne, .. für Rest.

Struct-Definitionen können generische Parameter haben.

struct Container<T> { item: T } ist die generische Form. Die impl-Blöcke wiederholen die Type-Parameter: impl<T> Container<T> { ... }. Mit Trait-Bounds: impl<T: Clone> Container<T>.

Strukturen mit Referenz-Feldern brauchen Lifetime-Parameter.

struct S { r: &T } ist Compile-Fehler. Lösung: struct S<'a> { r: &'a T }. Compiler kann implizit nichts inferieren, weil er keine Lebenszeit des Inhalts kennt.

pub struct mit privaten Feldern braucht Konstruktor-Funktion.

Wer einen Struct hat, dessen Felder alle privat sind, kann ihn nicht direkt mit MyStruct { ... } außerhalb des Moduls bauen. Lösung: eine pub fn neu(...)-Associated-Function als Eingangspunkt.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Structs & Methoden

Zur Übersicht