Salta ai contenuti

Errore Rust E0451: field of struct is private (letterale di struct)

E0451 significa: hai provato a costruire una struct con un letterale come Account { ... }, ma uno dei suoi campi è privato. Per scrivere un letterale di struct devi compilare tutti i campi, quindi basta un solo campo privato per lasciarti fuori. È voluto: il modulo vuole che tu passi dal suo costruttore. La soluzione è quasi sempre chiamare (o aggiungere) una funzione new. Questa pagina fa parte della guida agli errori del compilatore.

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, // ❌ private field
| ^^^^^^^ private field

Il compilatore ti dice: «il campo balance della struct Account è privato», e indica il punto del letterale in cui provi a impostarlo.

Ecco il programma che lo provoca:

mod bank {
pub struct Account {
pub owner: String,
balance: i64, // private
}
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, // ❌ private field
};
println!("{} has {}", account.owner, account.balance());
}

Potresti vedere invece uno dei suoi parenti stretti:

Cosa hai scritto Cosa dice rustc
il campo privato nel letterale error[E0451]: field `balance` of struct `Account` is private
Config { verbose: true, ..Default::default() } anche E0451, che indica ..Default::default() (vedi la Soluzione 3)
il letterale senza il campo privato cannot construct `Account` with struct literal syntax due to private fields, spesso con help: you might have meant to use the `new` associated function
Percent(150) per una tuple struct E0603: tuple struct constructor `Percent` is private

Si risolvono tutti allo stesso modo.

La freccia indica il campo che non puoi impostare: balance. owner è pub, quindi impostarlo va bene. Ma un letterale di struct è tutto o niente. Non puoi lasciare fuori balance (Rust non compila i campi mancanti), e non puoi nemmeno impostarlo. Quindi fuori da mod bank non c’è proprio modo di scrivere Account { ... }.

Immagina di aprire un vero conto in banca. Non puoi scrivere tu il saldo iniziale sul modulo. Scrivi il tuo nome, la banca crea il conto, e la banca decide che parte da zero. Account::new è quel modulo.

I campi delle struct sono privati per impostazione predefinita, proprio come funzioni e moduli (vedi Moduli). Se il codice esterno potesse costruire un Account con il saldo che vuole, le regole del modulo non varrebbero niente. Qualcuno potrebbe creare un conto con un milione, o con meno cinquanta. Siccome solo mod bank può scrivere il letterale, ogni Account del programma viene creato da codice che conosce le regole.

C’è un secondo vantaggio. Se più avanti aggiungi un campo privato, come created_at o history, il codice fuori dal modulo non si rompe, perché non ha mai scritto il letterale. Chiama new, e tu aggiorni new in un solo punto.

Soluzione 1: usa (o aggiungi) un costruttore pub fn new

Sezione intitolata “Soluzione 1: usa (o aggiungi) un costruttore pub fn new”

Scegli questa strada quasi sempre. Cerca prima una funzione new (o from_..., with_...). Spesso è rustc stesso a indicartela. Se la struct è tua e non ce n’è una, scrivila:

mod bank {
pub struct Account {
pub owner: String,
balance: i64, // privato
}
impl Account {
// Ogni conto parte da 0. I soldi entrano solo con deposit().
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!("{} ha {}", account.owner, account.balance());
}

Output:

Ferris ha 250
▶ Provalo tu

Dentro mod bank il letterale è permesso, quindi new può scrivere Account { ... } liberamente. Non dimenticare il pub su new stesso; senza ottieni E0624 (associated function `new` is private). Se alcuni input vanno rifiutati, fai restituire a new un Option<Account> o un Result<Account, String>; la pagina di E0603 ne mostra uno. Trovi di più sui costruttori in Struct.

Soluzione 2: rendi i campi pub quando i dati sono semplici

Sezione intitolata “Soluzione 2: rendi i campi pub quando i dati sono semplici”

Scegli questa strada quando qualsiasi combinazione di valori va bene. Un colore è solo tre numeri; non c’è un colore «sbagliato» da cui proteggersi. Allora un letterale è il modo più chiaro per costruirlo:

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

Output:

#f74c00
▶ Provalo tu

Ti serve pub su ogni campo. Se deve costruirla solo il tuo crate, va bene anche pub(crate) sui campi.

Quando invece tenere il campo privato: quando un valore potrebbe essere sbagliato (un saldo negativo, una percentuale sopra 100, un’email senza @), o quando due campi devono essere coerenti tra loro (un total che deve essere uguale alla somma degli items). Lì un letterale permetterebbe di saltare il controllo, e la scelta giusta è la Soluzione 1.

Soluzione 3: parti da Default, poi cambia i campi pubblici

Sezione intitolata “Soluzione 3: parti da Default, poi cambia i campi pubblici”

Scegli questa strada per le struct di «impostazioni» con valori predefiniti sensati, dove alcuni campi sono pubblici e altri no. Prima, la trappola. La sintassi di aggiornamento ..Default::default() sembra che debba funzionare, ma non funziona:

mod settings {
pub struct Config {
pub verbose: bool,
retries: u32, // private: must stay between 1 and 10
}
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() è comunque un letterale di struct: copia retries nella nuova struct, e questo conta come impostarlo. Crea invece prima il valore predefinito e cambia i campi pubblici dopo:

mod settings {
pub struct Config {
pub verbose: bool,
retries: u32, // privato: deve restare tra 1 e 10
}
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; // i campi pubblici si possono cambiare dopo
println!("verbose: {}, tentativi: {}", config.verbose, config.retries());
}

Output:

verbose: true, tentativi: 3
▶ Provalo tu

Se tutti i campi sono pub, ..Default::default() funziona benissimo. Lo bloccano solo i campi privati.

Scegli questa strada quando ci sono diverse opzioni e alcune hanno delle regole. Ogni metodo riceve la struct, cambia un’impostazione (controllandola) e restituisce la struct, così le chiamate si concatenano bene:

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
}
// La regola vive qui: da 1 a 10 tentativi.
pub fn retries(mut self, n: u32) -> Config {
self.retries = n.clamp(1, 10);
self
}
pub fn describe(&self) -> String {
format!("verbose: {}, tentativi: {}", self.verbose, self.retries)
}
}
}
fn main() {
let config = settings::Config::new().verbose(true).retries(50);
println!("{}", config.describe());
}

Output:

verbose: true, tentativi: 10
▶ Provalo tu

La richiesta di 50 tentativi è diventata 10 senza fare rumore, perché la regola vive in un solo punto. Molti crate fanno così, a volte con un tipo ConfigBuilder separato e una chiamata finale .build(). Vedi Design pattern per quella versione.

Dopo aver spostato una struct in un modulo. Ogni User { name, email } in main.rs funzionava finché User non è finito in mod user (o in src/user.rs). Ora falliscono tutti. Decidi struct per struct: i dati semplici hanno campi pub (Soluzione 2); tutto ciò che ha delle regole ha una pub fn new (Soluzione 1).

Tipi della libreria standard o di un crate. Non puoi scrivere Vec { ... } o Duration { ... }. rustc dice cannot construct `Vec<_, _>` with struct literal syntax due to private fields ed elenca funzioni come Vec::new() e Vec::with_capacity(_). Usa quelle, oppure Duration::from_secs(5).

Una struct #[non_exhaustive] di un altro crate. Qui non aiutano nemmeno i campi pubblici, e il codice è diverso: error[E0639]: cannot create non-exhaustive struct using struct expression. Il crate vuole poter aggiungere campi in futuro senza romperti il codice, quindi usa il suo costruttore o Default, esattamente come nelle Soluzioni 1 e 3.

  • E0616: lo stesso campo privato, ma mentre lo leggi (account.balance).
  • E0603: tuple struct constructor `X` is private, e funzioni, struct o moduli privati.
  • E0624: associated function `new` is private. Anche il tuo costruttore ha bisogno di pub.
  • E0560: struct `X` has no field named `y` . Un errore di battitura nel nome di un campo di un letterale.
  • E0063 (in inglese): missing field `x` in initializer of `Y` . Hai lasciato fuori un campo pubblico.
  • Tutti gli errori del compilatore: l’elenco completo delle pagine sugli errori.

Verifica veloce

1. pub struct Ticket { pub title: String, id: u32 } è in mod tickets. Come può main creare un Ticket?

2. Perché Config { verbose: true, ..Default::default() } fallisce quando Config ha un campo privato?

3. Quale struct va bene con tutti i campi pub e semplici letterali?

Punteggio: 0 / 3