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
struct Person {
name: String,
alter: u32,
ist_aktiv: bool,
}Die Syntax ist bewusst minimal gehalten. Das struct-Schlüsselwort leitet die Deklaration ein, danach folgt der Name in UpperCamelCase (das ist die Rust-Konvention für Typ-Namen — MyStruct, nicht my_struct oder MYSTRUCT). Innerhalb der geschweiften Klammern stehen die Felder, jeweils im name: Typ-Format, durch Kommas getrennt. Ein Trailing-Komma nach dem letzten Feld ist erlaubt und stilistisch üblich — er erleichtert das Hinzufügen weiterer Felder ohne nervige Komma-Anpassungen.
Wichtig: jedes Feld braucht eine explizite Typ-Annotation. Anders als bei let-Bindungen gibt es keine Field-Type-Inference — der Compiler weiß sonst nicht, wie viel Speicher der Struct belegt oder wie er die Felder ausrichtet. Die Felder können beliebige Typen haben: andere Structs, Referenzen (mit Lifetime-Parameter), generische Typ-Parameter, primitive Typen, Option/Vec/HashMap, oder eigene Newtype-Wrapper.
Konstruktion
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:
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 }Field-Init-Shorthand spart die redundante Wiederholung des Namens. Wenn du eine lokale Variable hast, die genauso heißt wie das Feld (was in Konstruktoren oft der Fall ist), reicht der Name allein — der Compiler weiß, dass name sowohl die Variable als auch das Feld bezeichnet.
Das ist nicht nur Schreibarbeit-Ersparnis, sondern auch ein Lesbarkeits-Gewinn: bei einem Struct mit zehn Feldern siehst du sofort, welche aus Parametern kommen (Shorthand) und welche explizit gesetzt werden (feld: ausdruck). Sehr typisch in Konstruktor-Funktionen, Builder-Settern und Pattern-Match-Ergebnis-Konstruktion.
Update-Syntax mit ..
Wenn du eine Instanz mit nur wenigen Änderungen zu einer bestehenden erstellen willst, gibt es die ..-Update-Syntax:
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 }Die Update-Syntax hat drei wichtige Eigenschaften, die du kennen musst, um sie korrekt einzusetzen.
..original muss als letztes stehen — die Reihenfolge im Struct-Literal ist sonst frei, aber der Spread-Operator ist syntaktisch der Schluss. Eine umgekehrte Reihenfolge wäre nicht eindeutig zu parsen.
Explizit gesetzte Felder haben Vorrang. Im Beispiel ist ist_aktiv: false der explizite Wert; alles andere kommt aus original. Wenn du das gleiche Feld doppelt setzt (einmal explizit, einmal über Spread), gewinnt die explizite Variante.
Andere Felder werden aus original gemoved (oder bei Copy-Typen: kopiert). Das hat Folgen: nach dem Update-Aufruf ist original möglicherweise nicht mehr vollständig nutzbar, weil seine Move-Felder weg sind. Bei Copy-Typen ist das egal — sie werden kopiert und original bleibt intakt.
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
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 zu verstehen: für jeden Schreibzugriff auf irgendein Feld muss die gesamte Struct-Bindung mit let mut deklariert sein. Anders als in manchen anderen Sprachen gibt es in Rust keine Per-Field-Mutability — du kannst nicht „nur das Feld X als mutable" markieren. Diese Vereinfachung passt zum Borrow-Modell: Rust verfolgt Mutabilität auf Bindungs-Ebene, nicht auf Feld-Ebene.
Wer feinere Kontrolle braucht — etwa „dieser Struct ist immutable, aber ein Cache-Feld darf sich ändern" — hat zwei Optionen: Cell<T> oder RefCell<T> als Wrapper für das Mutations-Feld (Interior Mutability). Damit kannst du auch durch eine &self-Methode den Feld-Inhalt verändern, ohne dass die äußere Struct-Bindung mutable sein muss. Details im Smart-Pointer-Kapitel.
Destrukturierung mit Patterns
Wie schon im let-Artikel: Structs lassen sich mit Patterns destrukturieren:
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 }Struct-Destrukturierung zerlegt eine Instanz in ihre Einzelfelder und bindet sie an Namen, die du im weiteren Scope nutzen kannst. Die drei Varianten — vollständig, selektiv mit .., oder mit Umbenennung — decken alle praktischen Bedürfnisse ab.
Besonders nützlich ist die Umbenennung mit feld: anderer_name: wenn die Feld-Namen aus dem Struct nicht zu deiner aktuellen Variablen-Naming-Konvention passen, kannst du sie umbenennen. Im Beispiel werden name und alter zu vorname und jahre umbenannt — sie heißen im Funktions-Kontext klarer. Pattern-Destrukturierung greift in let-Bindungen, in Funktions-Parametern und vor allem in match-Armen.
Patterns in Funktions-Parametern
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:
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:
// user.username = "..."; // ok — pub
// user.email = "..."; // ok — pub
// user.password_hash = "..."; // Fehler — privatDas ist klassische Encapsulation auf Feld-Ebene: jedes Feld bekommt seine eigene Sichtbarkeit. pub macht ein Feld außerhalb des Moduls lesbar (und schreibbar, sofern die Bindung mutable ist); ohne pub ist es nur innerhalb des Moduls zugänglich. Damit kannst du eine User-Struct exponieren, deren username und email Teil der öffentlichen API sind, deren password_hash aber strikt intern bleibt.
Diese Trennung ist wichtig für API-Design: Konsumenten der Library kommen nur an die öffentlichen Felder, die internen bleiben für interne Logik reserviert. Wenn du später die interne Repräsentation ändern willst (etwa von String-Hash zu Argon2-Output mit eigenem Typ), kannst du das tun, ohne die öffentliche API zu brechen.
Struct ohne pub-Felder — Black Box
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 }
}Wer Wert auf strikte Encapsulation legt, kann alle Felder privat halten und nur Methoden als API anbieten. AppConfig ist außerhalb des Moduls nur über neu, host und port zugänglich — die internen Felder bleiben unsichtbar.
Vorteil dieser Variante: maximale Flexibilität bei späteren Änderungen. Du kannst das interne Layout komplett umstellen, neue Felder hinzufügen, alte entfernen — solange die öffentlichen Methoden ihre Semantik behalten, ändert sich für Konsumenten nichts. Bei Public-Field-Structs (wie oben mit pub email: String) ist jede Feld-Änderung ein Breaking Change.
Bei API-Design ist die Faustregel: für interne Tools pub-Felder (kompakter); für Library-Crates strikte Kapselung mit Getter/Setter (zukunftssicherer).
Memory-Layout
Ein Struct belegt im Speicher genau die Summe seiner Felder plus eventuelles Padding für Alignment:
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 }:
xbelegt 1 Byte an Offset 0.ymuss auf 8-Byte-Grenze liegen → 7 Bytes Padding nachx.ybelegt 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):
#[repr(C)]
struct CKompatibel {
x: u8,
y: u64,
}Standardmäßig darf der Rust-Compiler die Felder umordnen, um Padding zu minimieren. Im Beispiel mit B { x: u8, y: u64 } würde er intern eher das Layout { y: u64, x: u8 } wählen — dann passt das u8 an die letzte Position, ohne dass Padding zwischen den Feldern nötig wird. Diese Optimierung ist transparent: aus deiner Code-Perspektive ändert sich nichts, der Compiler kümmert sich selbst um das effiziente Layout.
Bei #[repr(C)] wird diese Freiheit aufgehoben: die Felder liegen exakt in Deklarations-Reihenfolge im Speicher, mit C-konformen Padding-Regeln. Das ist Pflicht für FFI (Foreign Function Interface), wenn dein Rust-Struct mit einem C-Code austauschbar sein muss — sonst würden die Felder an unterschiedlichen Offsets liegen, und der C-Code würde Müll lesen. Für reines Rust-Code lässt du repr(C) weg und genießt die automatische Optimierung.
Praxis: Field-Structs im echten Code
User-Profil
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 }Eine typische User-Struct mit gemischter Sichtbarkeit. Die Identifikations- und Adress-Felder (id, username, email) sind pub — Konsumenten sollen sie direkt lesen können. erstellt_am ebenfalls, aber als reines Lese-Feld (kein Setter). email_verifiziert dagegen ist privat: der externe Code soll es nicht direkt setzen können, nur über kontrollierte Logik (etwa eine verifizieren-Methode, die zusätzliche Checks macht).
Der Konstruktor neu setzt die initialen Werte und versteckt die Initialisierungs-Komplexität (Aufruf von heute() für den Zeitstempel). Damit kommt der Aufrufer nicht direkt mit dem internen Layout in Berührung — die API ist klar und stabil.
2D-Vektor mit Update-Syntax
#[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);
}Eine klassische Vektor-Struct für 2D-Geometrie. Weil beide Felder f64 (also Copy) sind, kann der ganze Vec2 selbst Copy sein — #[derive(Copy, Clone)] macht das möglich. Damit verhält sich Vec2 wie ein Wert-Typ: du kannst ihn frei kopieren, by-value an Funktionen geben, ohne Ownership-Sorgen.
Die Update-Syntax mit ..p ist hier völlig unproblematisch: weil alle Felder Copy sind, wird kein partial Move ausgelöst. p_horizontal und p sind danach beide voll nutzbar. Bei einem Struct mit String- oder Vec-Feldern wäre das Spread-Verhalten anders — das Original würde Move-betroffene Felder verlieren.
const NULL als Associated Constant ist ein schönes Pattern: ein wichtiger Default-Wert wird direkt an der Typ-Definition exponiert, abrufbar als Vec2::NULL ohne Konstruktor-Aufruf.
HTTP-Request
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");
}Ein typischer Web-Domain-Struct. Vier Felder unterschiedlicher Typen — Strings für Method und Path, Vec für Headers, Bytes für den Body. Die get-Konstruktor-Funktion ist ein Convenience-Wrapper, der das häufigste Setup („eine GET-Anfrage an Path X") in einer Zeile macht; die Header-Methode erlaubt das schrittweise Hinzufügen von Headers.
impl Into<String> als Parameter-Typ ist ein nettes Detail: der Aufrufer kann sowohl &str-Literale ("/users/42") als auch besitzte Strings übergeben. Das Trait Into<String> ist für beide implementiert, und im Konstruktor läuft .into() einmal pro Aufruf, um das Argument in einen owned String zu konvertieren.
Config mit Pattern-Destrukturierung
struct Config {
host: String,
port: u16,
workers: u32,
log_level: String,
}
fn validiere(config: &Config) -> Result<(), String> {
let Config { host, port, workers, .. } = config;
if host.is_empty() { return Err(String::from("Host darf nicht leer sein")); }
if *port == 0 { return Err(String::from("Port 0 ist ungültig")); }
if *workers == 0 { return Err(String::from("Workers muss > 0 sein")); }
Ok(())
}Pattern-Destrukturierung in Funktions-Bodies ist eine elegante Form, mehrere Felder in einer Zeile in den Scope zu bringen. Statt config.host, config.port, config.workers an jeder Stelle zu schreiben, hast du danach die Felder als lokale Bindungen verfügbar.
Beim Beispiel siehst du auch die ..-Form: das log_level-Feld wird ignoriert, weil die Validierung es nicht braucht. Die *-Dereferenzierungen (*port, *workers) sind nötig, weil die Destrukturierung auf einer &Config-Referenz arbeitet — die Bindings sind also &u16, &u32, nicht direkte Werte.
State-Container
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; }
}
}Eine globale Statistik-Struktur, wie sie in jedem Monitoring-Setup vorkommt. Die Felder sind alle pub, weil Konsumenten sie direkt lesen müssen (für Dashboards, Health-Checks). Die anfrage_zaehlen-Methode kapselt die Update-Logik — der gesamt-Zähler steigt immer, der Fehler-Zähler nur bei war_fehler.
In einer Multi-Thread-Umgebung würde dieser Struct in einen Arc<Mutex<AppState>> gewickelt, damit gleichzeitige Zugriffe sicher sind. Für single-threaded Workloads (z. B. einen Single-Worker-Service) reicht die direkte Variante.
Nested Structs
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);
}Verschachtelte Structs sind die natürliche Form, um komplexe Domain-Objekte zu modellieren. Der Kunde enthält eine Adresse als Wert-Feld (rechnungs_adresse: Adresse) und eine optionale zweite Adresse über Option<Adresse>. Der Option-Wrapper macht klar: die Liefer-Adresse ist nicht jedem Kunden bekannt, der Code muss den None-Fall behandeln.
Zugriff auf verschachtelte Felder erfolgt mit Punkt-Notation: k.rechnungs_adresse.stadt. Bei Option-Feldern brauchst du Pattern-Matching: if let Some(addr) = &k.liefer_adresse { addr.stadt }. Diese Form ist deutlich sicherer als Null-Pointer-basierte Lösungen in anderen Sprachen — der Compiler zwingt dich, mit dem Fehlen umzugehen.
Generische Struct mit Type-Parametern — Vorgriff
Das folgende Beispiel nutzt Generics (
Paginated<T>), damit der Container mit verschiedenen Item-Typen funktioniert. Generics werden im eigenen Kapitel ausführlich behandelt; hier nur ein Vorgriff auf das Konzept.
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());
}Eine generische Pagination-Struct, die mit beliebigen Item-Typen funktioniert. Der <T>-Parameter wird vom Aufrufer bei der Konstruktion festgelegt — Paginated<User>, Paginated<Order>, Paginated<String> —, und Rust generiert für jede konkrete Instanziierung eine spezialisierte Variante (Monomorphisierung).
Im impl<T> Paginated<T> siehst du die wiederholte Type-Parameter-Deklaration: das <T> nach impl führt den Parameter im Scope ein, und Paginated<T> ist die Struct, auf der die Methoden definiert werden. Bei Bedarf kannst du Trait-Bounds hinzufügen: impl<T: Clone> Paginated<T> würde nur für Clone-Typen gelten.
Struct mit Referenz-Feld und Lifetime
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]
}
}Eine Struct mit einem Referenz-Feld braucht zwingend einen Lifetime-Parameter — sonst weiß der Compiler nicht, wie lange die Referenz gültig sein darf. Das <'a> an TextSegment ist die Lebenszeit, die sowohl an text als auch implizit an die ganze Struct-Instanz gebunden ist.
Damit garantiert das Typ-System: ein TextSegment lebt nicht länger als der ursprüngliche String, aus dem es konstruiert wurde. Wenn du versuchst, ein TextSegment über das Ende seines Quellstring hinaus zu behalten, gibt es einen Compile-Fehler. Diese Mechanik ist die Grundlage für zero-copy Parser, View-Strukturen und alle anderen Patterns, die mit geliehenen Daten arbeiten.
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
- The Rust Book – Defining and Instantiating Structs
- Rust Reference – Struct types
- Rust Reference – Struct Items
- Rust Reference – Type Layout
- Rust API Guidelines – Naming