Aller au contenu

Erreur Rust E0451 : field of struct is private (champ privé à la création)

E0451 veut dire : vous avez essayé de construire une struct avec un littéral comme Account { ... }, mais l’un de ses champs est privé. Pour écrire un littéral de struct, il faut remplir chaque champ, donc un seul champ privé suffit à vous bloquer. C’est voulu : le module veut que vous passiez par son propre constructeur. La solution consiste presque toujours à appeler (ou à ajouter) une fonction new. Cette page fait partie du guide des erreurs du compilateur Rust.

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

Le compilateur vous dit : « le champ balance de la struct Account est privé ». La ligne in this type (dans ce type) montre quelle struct vous étiez en train de construire.

Voici le programme qui la provoque :

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

(Les commentaires disent : balance est privé, et la ligne marquée ❌ essaie de remplir ce champ privé.)

Vous verrez peut-être plutôt l’une de ses proches cousines :

Ce que vous avez écrit Ce que dit rustc
le champ privé dans le littéral error[E0451]: field `balance` of struct `Account` is private
Config { verbose: true, ..Default::default() } E0451 aussi, avec la flèche sur ..Default::default() (voir la solution 3)
le littéral sans le champ privé cannot construct `Account` with struct literal syntax due to private fields, souvent avec help: you might have meant to use the `new` associated function
Percent(150) pour une tuple struct E0603 : tuple struct constructor `Percent` is private

La troisième ligne veut dire « impossible de construire Account avec un littéral de struct à cause de champs privés », et le help vous suggère d’utiliser la fonction associée new. Toutes ces erreurs se corrigent de la même façon.

La flèche pointe sur le champ que vous n’avez pas le droit de remplir : balance. owner est pub, donc le remplir ne pose aucun problème. Mais un littéral de struct, c’est tout ou rien. Vous ne pouvez pas omettre balance (Rust ne remplit pas les champs manquants), et vous ne pouvez pas non plus le remplir. En dehors de mod bank, il n’y a donc tout simplement aucun moyen d’écrire Account { ... }.

Imaginez que vous ouvrez un vrai compte en banque. Vous n’écrivez pas vous-même votre solde de départ sur le formulaire. Vous remplissez votre nom, la banque crée le compte, et c’est la banque qui décide qu’il commence à zéro. Account::new, c’est ce formulaire.

Les champs des structs sont privés par défaut, tout comme les fonctions et les modules (voir Les modules). Si du code extérieur pouvait construire un Account avec le solde de son choix, les règles du module ne serviraient à rien. Quelqu’un pourrait créer un compte avec un million, ou avec moins cinquante. Comme seul mod bank peut écrire le littéral, chaque Account du programme est créé par du code qui connaît les règles.

Il y a un deuxième avantage. Si vous ajoutez plus tard un champ privé, comme created_at ou history, le code à l’extérieur du module ne casse pas, car il n’a jamais écrit le littéral. Il appelle new, et vous mettez new à jour à un seul endroit.

Solution 1 : utiliser (ou ajouter) un constructeur pub fn new

Section intitulée « Solution 1 : utiliser (ou ajouter) un constructeur pub fn new »

Choisissez-la la plupart du temps. Cherchez d’abord une fonction new (ou from_..., with_...) ; rustc vous l’indique souvent. Si c’est votre propre struct et qu’il n’y en a pas, écrivez-en une :

mod bank {
pub struct Account {
pub owner: String,
balance: i64, // privé
}
impl Account {
// Chaque compte commence à 0. L'argent n'arrive que par 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!("{} a {}", account.owner, account.balance());
}

Sortie :

Ferris a 250
▶ Essayez par vous-même

À l’intérieur de mod bank, le littéral est autorisé, donc new peut écrire Account { ... } librement. N’oubliez pas le pub sur new lui-même ; sans lui, vous obtenez E0624 (associated function `new` is private, la fonction associée new est privée). Si certaines valeurs doivent être refusées, faites renvoyer à new une Option<Account> ou un Result<Account, String> ; la page E0603 en montre un exemple. Pour en savoir plus sur les constructeurs, voir Les structs.

Solution 2 : rendre les champs pub quand les données sont simples

Section intitulée « Solution 2 : rendre les champs pub quand les données sont simples »

Choisissez-la quand n’importe quelle combinaison de valeurs convient. Une couleur, ce n’est que trois nombres ; il n’y a pas de « mauvaise » couleur contre laquelle se protéger. Dans ce cas, un littéral est la façon la plus claire de la construire :

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

Sortie :

#f74c00
▶ Essayez par vous-même

Il faut pub sur chaque champ. Si seule votre propre crate doit pouvoir la construire, pub(crate) sur les champs marche aussi.

Quand garder le champ privé : quand une valeur peut être incorrecte (un solde négatif, un pourcentage au-dessus de 100, un e-mail sans @), ou quand deux champs doivent rester cohérents entre eux (un total qui doit être égal à la somme des items). Un littéral permettrait alors de sauter la vérification, et la solution 1 est la bonne.

Solution 3 : partir de Default, puis modifier les champs publics

Section intitulée « Solution 3 : partir de Default, puis modifier les champs publics »

Choisissez-la pour les structs de « réglages » qui ont des valeurs par défaut raisonnables, avec certains champs publics et d’autres non. Voyons d’abord le piège. La syntaxe de mise à jour de struct ..Default::default() a l’air de devoir marcher, mais ce n’est pas le cas :

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

(Le commentaire dit : privé, doit rester entre 1 et 10.) Rustc signale que le champ retries est privé. ..Default::default() reste un littéral de struct : il copie retries dans la nouvelle struct, et ça compte comme remplir ce champ. À la place, créez d’abord la valeur par défaut, puis modifiez les champs publics :

mod settings {
pub struct Config {
pub verbose: bool,
retries: u32, // privé : doit rester entre 1 et 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; // les champs publics peuvent être modifiés ensuite
println!("verbose : {}, retries : {}", config.verbose, config.retries());
}

Sortie :

verbose : true, retries : 3
▶ Essayez par vous-même

Si tous les champs sont pub, ..Default::default() fonctionne très bien. Ce sont seulement les champs privés qui le bloquent.

Solution 4 : proposer des méthodes de style builder

Section intitulée « Solution 4 : proposer des méthodes de style builder »

Choisissez-la quand il y a plusieurs options et que certaines ont des règles. Chaque méthode prend la struct, change un réglage (en le vérifiant) et rend la struct, ce qui permet d’enchaîner joliment les appels :

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 règle vit ici : de 1 à 10 tentatives.
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());
}

Sortie :

verbose : true, retries : 10
▶ Essayez par vous-même

La demande de 50 tentatives est devenue 10 sans bruit, parce que la règle vit à un seul endroit. Beaucoup de crates font ainsi, parfois avec un type ConfigBuilder séparé et un appel final à .build(). Voir Les patrons de conception pour cette version.

Après avoir déplacé une struct dans un module. Chaque User { name, email } de main.rs fonctionnait, jusqu’à ce que User parte dans mod user (ou dans src/user.rs). Maintenant, ils échouent tous. Décidez struct par struct : les données simples reçoivent des champs pub (solution 2) ; tout ce qui a des règles reçoit une pub fn new (solution 1).

Les types de la bibliothèque standard ou d’une crate. Vous ne pouvez pas écrire Vec { ... } ni Duration { ... }. Rustc dit cannot construct `Vec<_, _>` with struct literal syntax due to private fields (impossible de construire Vec avec un littéral à cause de champs privés) et liste des fonctions comme Vec::new() et Vec::with_capacity(_). Utilisez-les, ou Duration::from_secs(5).

Une struct #[non_exhaustive] d’une autre crate. Ici, même des champs publics ne suffisent pas, et le code d’erreur est différent : error[E0639]: cannot create non-exhaustive struct using struct expression (impossible de créer une struct non exhaustive avec une expression de struct). La crate veut pouvoir ajouter des champs plus tard sans casser votre code : utilisez son constructeur ou Default, exactement comme dans les solutions 1 et 3.

  • E0616 : le même champ privé, mais au moment de le lire (account.balance).
  • E0603 : tuple struct constructor `X` is private, et les fonctions, structs ou modules privés.
  • E0624 : associated function `new` is private. Votre constructeur a besoin de pub lui aussi.
  • E0560 : struct `X` has no field named `y` (la struct n’a pas de champ de ce nom). Une faute de frappe dans le nom d’un champ du littéral.
  • E0063 (en anglais) : missing field `x` in initializer of `Y` (champ manquant). Vous avez oublié un champ public.
  • Toutes les erreurs du compilateur : la liste complète des pages d’erreurs.

Petit quiz

1. pub struct Ticket { pub title: String, id: u32 } se trouve dans mod tickets. Comment main peut-il créer un Ticket ?

2. Pourquoi Config { verbose: true, ..Default::default() } échoue-t-il quand Config a un champ privé ?

3. Quelle struct convient bien à des champs tous pub et à de simples littéraux ?

Score : 0 / 3