Zum Inhalt springen

Rust-Fehler E0451: field of struct is private (Struct-Literal)

E0451 bedeutet: Du wolltest ein Struct mit einem Literal wie Account { ... } bauen, aber eines seiner Felder ist privat. In einem Struct-Literal musst du jedes Feld ausfüllen. Ein einziges privates Feld reicht also, damit du draußen bleibst. Das ist Absicht: Das Modul will, dass du seinen eigenen Konstruktor benutzt. Die Lösung ist fast immer, eine new-Funktion aufzurufen (oder zu ergänzen). Diese Seite gehört zur Reihe Rust-Compilerfehler einfach erklärt.

error[E0451]: field `balance` of struct `Account` is private
--> src/main.rs:21:9
|
19 | let account = bank::Account {
| ------------- in this type
20 | owner: String::from("Ferris"),
21 | balance: 1_000_000, // ❌ privates Feld
| ^^^^^^^ private field

Auf Deutsch heißt das: „Das Feld balance des Structs Account ist privat.“ Die Markierung in this type zeigt auf das Struct, das du gerade baust. Dieses Programm erzeugt die Meldung:

mod bank {
pub struct Account {
pub owner: String,
balance: i64, // privat
}
impl Account {
pub fn new(owner: &str) -> Account {
Account { owner: owner.to_string(), balance: 0 }
}
pub fn balance(&self) -> i64 {
self.balance
}
}
}
fn main() {
let account = bank::Account {
owner: String::from("Ferris"),
balance: 1_000_000, // ❌ privates Feld
};
println!("{} has {}", account.owner, account.balance());
}

Vielleicht siehst du stattdessen einen seiner nahen Verwandten:

Was du geschrieben hast Was rustc sagt
das private Feld im Literal error[E0451]: field `balance` of struct `Account` is private
Config { verbose: true, ..Default::default() } ebenfalls E0451, mit dem Pfeil auf ..Default::default() (siehe Lösung 3)
das Literal ohne das private Feld cannot construct `Account` with struct literal syntax due to private fields („wegen privater Felder nicht mit Literal-Syntax erzeugbar“), oft mit help: you might have meant to use the `new` associated function
Percent(150) bei einem Tuple Struct E0603: tuple struct constructor `Percent` is private

Alle lassen sich auf dieselbe Weise beheben.

Der Pfeil zeigt auf das Feld, das du nicht setzen darfst: balance. owner ist pub, das darfst du setzen. Aber ein Struct-Literal gilt ganz oder gar nicht. Du kannst balance nicht weglassen (Rust füllt fehlende Felder nicht selbst aus), und setzen darfst du es auch nicht. Außerhalb von mod bank gibt es also schlicht keine Möglichkeit, Account { ... } zu schreiben.

Stell dir vor, du eröffnest ein echtes Bankkonto. Deinen Startbetrag darfst du nicht selbst ins Formular schreiben. Du trägst deinen Namen ein, die Bank legt das Konto an, und die Bank entscheidet, dass es bei null beginnt. Account::new ist dieses Formular.

Die Felder eines Structs sind standardmäßig privat, genau wie Funktionen und Module (siehe Module). Könnte Code von außen ein Account mit beliebigem Kontostand bauen, wären die Regeln des Moduls wertlos. Jemand könnte ein Konto mit einer Million anlegen – oder mit minus fünfzig. Weil nur mod bank das Literal schreiben darf, wird jedes Account im Programm von Code erzeugt, der die Regeln kennt.

Es gibt noch einen zweiten Vorteil. Ergänzt du später ein privates Feld wie created_at oder history, bricht Code außerhalb des Moduls nicht, denn er hat das Literal nie geschrieben. Er ruft new auf, und du passt new an einer einzigen Stelle an.

Lösung 1: Einen pub fn new-Konstruktor benutzen (oder ergänzen)

Abschnitt betitelt „Lösung 1: Einen pub fn new-Konstruktor benutzen (oder ergänzen)“

Nimm diese Lösung in den meisten Fällen. Such zuerst nach einer Funktion new (oder from_..., with_...). rustc weist dich oft darauf hin. Ist es dein eigenes Struct und es gibt keine, schreib eine:

mod bank {
pub struct Account {
pub owner: String,
balance: i64, // privat
}
impl Account {
// Jedes Konto beginnt bei 0. Geld kommt nur über deposit() hinein.
pub fn new(owner: &str) -> Account {
Account { owner: owner.to_string(), balance: 0 }
}
pub fn deposit(&mut self, amount: i64) {
if amount > 0 {
self.balance += amount;
}
}
pub fn balance(&self) -> i64 {
self.balance
}
}
}
fn main() {
let mut account = bank::Account::new("Ferris");
account.deposit(250);
println!("{} hat {}", account.owner, account.balance());
}

Ausgabe:

Ferris hat 250
▶ Selbst ausprobieren

Innerhalb von mod bank ist das Literal erlaubt, also darf new ganz normal Account { ... } schreiben. Vergiss das pub an new selbst nicht. Ohne bekommst du E0624 (associated function `new` is private). Sollen manche Eingaben abgelehnt werden, lass new ein Option<Account> oder Result<Account, String> zurückgeben. Die Seite zu E0603 zeigt ein Beispiel. Mehr über Konstruktoren findest du unter Structs.

Lösung 2: Die Felder pub machen, wenn es einfache Daten sind

Abschnitt betitelt „Lösung 2: Die Felder pub machen, wenn es einfache Daten sind“

Nimm diese Lösung, wenn jede Kombination von Werten in Ordnung ist. Eine Farbe besteht nur aus drei Zahlen. Es gibt keine „falsche“ Farbe, vor der man sich schützen müsste. Dann ist ein Literal der klarste Weg, sie zu bauen:

mod theme {
pub struct Color {
pub r: u8,
pub g: u8,
pub b: u8,
}
}
fn main() {
let ferris_orange = theme::Color { r: 247, g: 76, b: 0 };
println!("#{:02x}{:02x}{:02x}", ferris_orange.r, ferris_orange.g, ferris_orange.b);
}

Ausgabe:

#f74c00
▶ Selbst ausprobieren

Du brauchst pub an jedem Feld. Soll nur dein eigenes Crate das Struct bauen dürfen, funktioniert auch pub(crate) an den Feldern.

Wann das Feld besser privat bleibt: wenn ein Wert falsch sein kann (ein negativer Kontostand, ein Prozentwert über 100, eine E-Mail-Adresse ohne @) oder wenn zwei Felder zueinander passen müssen (ein total, das der Summe von items entsprechen muss). Dann könnte man mit einem Literal die Prüfung überspringen, und Lösung 1 ist die richtige Wahl.

Lösung 3: Mit Default starten und dann die öffentlichen Felder ändern

Abschnitt betitelt „Lösung 3: Mit Default starten und dann die öffentlichen Felder ändern“

Nimm diese Lösung für Einstellungs-Structs mit sinnvollen Standardwerten, bei denen manche Felder öffentlich sind und manche nicht. Zuerst die Falle: Die Struct-Update-Syntax ..Default::default() sieht aus, als müsste sie funktionieren. Tut sie aber nicht:

mod settings {
pub struct Config {
pub verbose: bool,
retries: u32, // privat: muss zwischen 1 und 10 bleiben
}
impl Default for Config {
fn default() -> Config {
Config { verbose: false, retries: 3 }
}
}
}
fn main() {
let config = settings::Config { verbose: true, ..Default::default() };
println!("{}", config.verbose);
}
error[E0451]: field `retries` of struct `Config` is private
--> src/main.rs:15:54
|
15 | let config = settings::Config { verbose: true, ..Default::default() };
| ^^^^^^^^^^^^^^^^^^ field `retries` is private

..Default::default() ist trotzdem ein Struct-Literal: Es kopiert retries in das neue Struct, und das zählt als Setzen. Erzeuge stattdessen zuerst den Standardwert und ändere danach die öffentlichen Felder:

mod settings {
pub struct Config {
pub verbose: bool,
retries: u32, // privat: muss zwischen 1 und 10 bleiben
}
impl Default for Config {
fn default() -> Config {
Config { verbose: false, retries: 3 }
}
}
impl Config {
pub fn retries(&self) -> u32 {
self.retries
}
}
}
fn main() {
let mut config = settings::Config::default();
config.verbose = true; // öffentliche Felder darf man danach ändern
println!("verbose: {}, retries: {}", config.verbose, config.retries());
}

Ausgabe:

verbose: true, retries: 3
▶ Selbst ausprobieren

Ist jedes Feld pub, funktioniert ..Default::default() problemlos. Nur private Felder blockieren es.

Nimm diese Lösung, wenn es mehrere Optionen gibt und für manche davon Regeln gelten. Jede Methode nimmt das Struct, ändert eine Einstellung (und prüft sie) und gibt das Struct zurück. So lassen sich die Aufrufe schön verketten:

mod settings {
pub struct Config {
verbose: bool,
retries: u32,
}
impl Config {
pub fn new() -> Config {
Config { verbose: false, retries: 3 }
}
pub fn verbose(mut self, on: bool) -> Config {
self.verbose = on;
self
}
// Hier wohnt die Regel: 1 bis 10 Versuche.
pub fn retries(mut self, n: u32) -> Config {
self.retries = n.clamp(1, 10);
self
}
pub fn describe(&self) -> String {
format!("verbose: {}, retries: {}", self.verbose, self.retries)
}
}
}
fn main() {
let config = settings::Config::new().verbose(true).retries(50);
println!("{}", config.describe());
}

Ausgabe:

verbose: true, retries: 10
▶ Selbst ausprobieren

Aus den gewünschten 50 Versuchen wurden stillschweigend 10, weil die Regel an genau einer Stelle wohnt. Viele Crates machen das so, manchmal mit einem eigenen Typ ConfigBuilder und einem abschließenden Aufruf von .build(). Diese Variante zeigt die Seite Entwurfsmuster.

Nachdem du ein Struct in ein Modul verschoben hast. Jedes User { name, email } in main.rs hat funktioniert, bis User nach mod user (oder src/user.rs) umgezogen ist. Jetzt schlagen alle fehl. Entscheide für jedes Struct einzeln: Einfache Daten bekommen pub-Felder (Lösung 2). Alles mit Regeln bekommt ein pub fn new (Lösung 1).

Typen aus der Standardbibliothek oder einem Crate. Vec { ... } oder Duration { ... } kannst du nicht schreiben. rustc sagt cannot construct `Vec<_, _>` with struct literal syntax due to private fields und listet Funktionen wie Vec::new() und Vec::with_capacity(_) auf. Nimm diese, oder Duration::from_secs(5).

Ein #[non_exhaustive]-Struct aus einem anderen Crate. Hier helfen nicht einmal öffentliche Felder, und der Fehlercode ist ein anderer: error[E0639]: cannot create non-exhaustive struct using struct expression („ein nicht erschöpfendes Struct kann nicht per Struct-Ausdruck erzeugt werden“). Das Crate will später Felder ergänzen können, ohne deinen Code zu brechen. Nimm also seinen Konstruktor oder Default, genau wie in Lösung 1 und Lösung 3.

  • E0616: dasselbe private Feld, aber beim Lesen (account.balance).
  • E0603: tuple struct constructor `X` is private sowie private Funktionen, Structs oder Module.
  • E0624: associated function `new` is private. Auch dein Konstruktor braucht pub.
  • E0560: struct `X` has no field named `y` . Ein Tippfehler im Feldnamen eines Literals.
  • E0063 (auf Englisch): missing field `x` in initializer of `Y` . Du hast ein öffentliches Feld weggelassen.
  • Alle Compilerfehler: die vollständige Liste der Fehlerseiten.

Kurzer Check

1. pub struct Ticket { pub title: String, id: u32 } steht in mod tickets. Wie kann main ein Ticket erzeugen?

2. Warum schlägt Config { verbose: true, ..Default::default() } fehl, wenn Config ein privates Feld hat?

3. Welches Struct eignet sich gut für lauter pub-Felder und einfache Literale?

Punkte: 0 / 3