Aller au contenu

Erreur Rust E0616 : field of struct is private (champ privé)

E0616 veut dire : le champ que vous demandez existe bien, mais il est privé, réservé au module où la struct est définie. Vous avez le droit de manipuler la struct, mais pas de fouiller à l’intérieur. Ça arrive à tout le monde la première fois qu’on déplace une struct dans son propre module, et à tous ceux qui tapent v.len au lieu de v.len(). La solution est généralement un appel de méthode. Cette page fait partie du guide des erreurs du compilateur Rust.

error[E0616]: field `balance` of struct `Account` is private
--> src/main.rs:16:50
|
16 | println!("{} has {}", account.owner, account.balance); // ❌ private field
| ^^^^^^^ private field

Le compilateur vous dit : « le champ balance de la struct Account est privé ». La flèche porte l’étiquette « private field » (champ privé).

Voici le programme qui la provoque :

mod bank {
pub struct Account {
pub owner: String,
balance: u64, // private
}
impl Account {
pub fn new(owner: &str) -> Account {
Account { owner: owner.to_string(), balance: 0 }
}
}
}
fn main() {
let account = bank::Account::new("Ferris");
println!("{} has {}", account.owner, account.balance); // ❌ private field
}

(Les commentaires disent : balance est privé, et la dernière ligne lit un champ privé.)

La même erreur existe en plusieurs variantes :

Ce que vous avez écrit Ce que dit rustc Solution
account.balance field `balance` of struct `Account` is private Solution 2 ou 3
scores.len sur un Vec field `len` of struct `Vec` is private, plus help: a method `len` also exists, call it with parentheses Solution 1
timeout.secs sur une Duration field `secs` of struct `Duration` is private Solution 1 (as_secs())
account.balance = 500 la même E0616 : l’écriture est bloquée aussi Solution 2

Le message help de la deuxième ligne veut dire : « une méthode len existe aussi, appelez-la avec des parenthèses ».

La flèche pointe sur le nom du champ, après le point. account.owner, sur la même ligne, ne pose aucun problème, car owner est marqué pub. balance ne l’est pas : seul le code à l’intérieur de mod bank peut le lire ou le modifier.

Remarquez que Account lui-même est pub. C’est ce qui surprend le plus : rendre une struct publique ne rend pas ses champs publics. Chaque champ reçoit son propre pub, ou reste privé.

Pensez à une banque. Vous pouvez entrer, et tout le monde peut voir le nom inscrit sur votre compte. Mais vous ne pouvez pas passer derrière le guichet pour ouvrir le coffre et compter l’argent. Vous demandez au guichetier, et le guichetier suit les règles de la banque.

En Rust, tout est privé par défaut : les fonctions, les modules, et aussi les champs des structs (voir Les modules). La visibilité dépend des modules, pas des types. N’importe quel code dans mod bank peut toucher à balance, et le code à l’extérieur ne le peut pas.

Le but est de permettre à une struct de faire respecter ses propres règles. Imaginons qu’un solde bancaire ne doive jamais devenir négatif. Si n’importe quelle partie du programme pouvait écrire account.balance = ..., n’importe laquelle pourrait briser cette règle, et il faudrait fouiller tout le code pour trouver la coupable. Si le champ est privé, seules les quelques méthodes de mod bank peuvent le modifier. Vérifiez la règle à cet endroit, et elle est respectée partout. Cette idée s’appelle l’encapsulation.

La bibliothèque standard fait pareil. Un Vec a des champs privés pour sa longueur et pour sa zone mémoire. Si vous pouviez écrire v.len = 100 sur un vecteur qui contient 3 éléments, lire v[50] lirait de la mémoire au hasard. Garder len privé, c’est ce qui rend Vec sûr à utiliser.

Solution 1 : appeler la méthode au lieu de lire le champ

Section intitulée « Solution 1 : appeler la méthode au lieu de lire le champ »

Choisissez-la quand la struct vient de la bibliothèque standard ou d’une autre crate. Vous ne pouvez pas modifier leur code, et elles vous donnent toujours des méthodes pour ce que vous avez le droit de voir. Quand rustc dit help: a method ... also exists, ajoutez les () :

use std::time::Duration;
fn main() {
let scores = vec![10, 20, 30];
println!("{} scores", scores.len());
let timeout = Duration::from_secs(90);
println!("Délai : {} secondes", timeout.as_secs());
}

Sortie :

3 scores
Délai : 90 secondes
▶ Essayez par vous-même

Le nom de la méthode n’est pas toujours celui du champ (secs se lit avec as_secs()). Regardez la page du type sur docs.rs, ou laissez votre éditeur lister les méthodes quand vous tapez le point.

Solution 2 : ajouter un accesseur, et des méthodes qui respectent les règles

Section intitulée « Solution 2 : ajouter un accesseur, et des méthodes qui respectent les règles »

Choisissez-la quand c’est votre propre struct et que le champ a une règle à protéger. Gardez le champ privé, ajoutez une pub fn qui renvoie sa valeur (un accesseur, ou « getter »), et ne le modifiez qu’à travers des méthodes qui vérifient la règle :

mod bank {
pub struct Account {
pub owner: String,
balance: i64, // privé : seul ce module peut le modifier
}
impl Account {
pub fn new(owner: &str) -> Account {
Account { owner: owner.to_string(), balance: 0 }
}
// Accesseur : tout le monde peut lire le solde.
pub fn balance(&self) -> i64 {
self.balance
}
pub fn deposit(&mut self, amount: i64) {
if amount > 0 {
self.balance += amount;
}
}
// La règle vit ici : le solde ne descend jamais sous zéro.
pub fn withdraw(&mut self, amount: i64) -> Result<(), String> {
if amount > self.balance {
return Err(format!("seulement {} disponible", self.balance));
}
self.balance -= amount;
Ok(())
}
}
}
fn main() {
let mut account = bank::Account::new("Ferris");
account.deposit(100);
println!("{} a {}", account.owner, account.balance());
match account.withdraw(500) {
Ok(()) => println!("500 retirés"),
Err(e) => println!("Impossible de retirer 500 : {e}"),
}
println!("Le solde est toujours de {}", account.balance());
}

Sortie :

Ferris a 100
Impossible de retirer 500 : seulement 100 disponible
Le solde est toujours de 100
▶ Essayez par vous-même

Rust permet à une méthode et à un champ de porter le même nom, donc balance() est le nom habituel de l’accesseur (pas get_balance()). Une fois l’accesseur écrit, rustc le suggère même : help: a method `balance` also exists, call it with parentheses. deposit et withdraw jouent le rôle de mutateurs (« setters ») : ils modifient le champ, mais seulement de la façon que les règles autorisent. Voir Les structs pour en savoir plus sur les méthodes.

Solution 3 : rendre le champ pub quand les données sont simples

Section intitulée « Solution 3 : rendre le champ pub quand les données sont simples »

Choisissez-la quand n’importe quelle valeur convient et que rien d’autre ne dépend du champ. Un point sur une grille, une couleur ou une struct de réglages n’ont aucune règle à protéger. Des accesseurs ne feraient qu’ajouter du code à taper :

mod geometry {
pub struct Point {
pub x: i32,
pub y: i32,
}
}
fn main() {
let mut p = geometry::Point { x: 3, y: 4 };
p.x += 1;
println!("Point en ({}, {})", p.x, p.y);
}

Sortie :

Point en (4, 4)
▶ Essayez par vous-même

Si seul votre propre code doit voir le champ, écrivez pub(crate) x: i32 (n’importe quel module de cette crate) ou pub(super) x: i32 (seulement le module parent). Dans un programme, ils se comportent comme pub ; dans une bibliothèque, ils gardent le champ hors de votre API publique. La page E0603 contient un tableau de tous les niveaux.

Faut-il simplement le rendre pub ? Posez-vous la question : « Est-ce qu’une certaine valeur de ce champ, ou une modification de ce seul champ, pourrait rendre la struct incorrecte ? » Un solde négatif, un len qui ne correspond pas aux données, un total qui n’est plus égal à la somme des articles : gardez ces champs privés et utilisez la solution 2. Si la réponse est « non, n’importe quelle valeur convient », pub est le choix honnête.

len sur un Vec. Vous avez écrit scores.len et obtenu field `len` of struct `Vec` is private. C’est parce que Vec a vraiment un champ privé appelé len. La correction : scores.len(). Détail amusant : name.len sur une String donne une autre erreur, E0615 (attempted to take value of method, tentative de prendre la valeur d’une méthode), parce que String n’a aucun champ de ce nom. Même correction.

Modifier un champ depuis l’extérieur. account.balance = 500; donne la même E0616 : l’écriture est bloquée elle aussi. Ne rendez pas le champ pub juste pour le modifier ; ajoutez une méthode comme deposit qui fait le changement et le vérifie.

Après avoir déplacé une struct dans son propre module ou fichier. Le code de main.rs utilisait user.email depuis des mois. Puis vous avez déplacé User dans src/user.rs, et chaque accès à un champ a cassé. Les champs qui marchaient avant sont maintenant privés à mod user. Ajoutez pub aux champs qui sont de simples données, et des accesseurs pour les autres.

Les tests. Un bloc #[cfg(test)] mod tests dans le même fichier est un module enfant : il peut donc lire les champs privés. Les tests du dossier tests/ sont en dehors de votre crate et ne peuvent utiliser que l’API pub, comme n’importe quel autre utilisateur.

  • E0603 : function `x` is private (la fonction est privée). La même idée pour les fonctions, les structs et les modules.
  • E0451 : le même champ privé, mais au moment de créer la struct avec Account { ... }.
  • E0624 : method `x` is private (la méthode est privée). La méthode (ou new) a besoin de pub elle aussi.
  • E0609 : no field `x` on type `T` (aucun champ de ce nom). Le champ n’existe pas du tout (souvent une faute de frappe).
  • E0615 : attempted to take value of method. Vous avez écrit x.len en voulant dire x.len().
  • Toutes les erreurs du compilateur : la liste complète des pages d’erreurs.

Petit quiz

1. pub struct Account { balance: u64 } se trouve dans mod bank. Est-ce que main peut lire account.balance ?

2. Vous obtenez `field len of struct Vec is private`. Quelle est la correction ?

3. Une struct Score a un champ points qui doit rester entre 0 et 100. Quelle est la meilleure conception ?

Score : 0 / 3