Erreur Rust E0609 : no field X on type T (champ inexistant)
E0609 veut dire : vous avez écrit value.something, mais le type de value n’a aucun champ appelé something. La plupart du temps, c’est une faute de frappe, ou alors la valeur n’est pas tout à fait du type que vous croyez (une Option<User> au lieu d’un User, par exemple). Le message d’erreur vous donne le type exact que Rust voit, et c’est en général tout ce qu’il vous faut pour corriger. (Cette page fait partie de Les erreurs du compilateur Rust expliquées.)
Le message d’erreur
Section intitulée « Le message d’erreur »error[E0609]: no field `emial` on type `User` --> src/main.rs:12:25 |12 | println!("{}", user.emial); // ❌ typo: the field is `email` | ^^^^^ unknown field |help: a field with a similar name exists |12 - println!("{}", user.emial); // ❌ typo: the field is `email`12 + println!("{}", user.email); // ❌ typo: the field is `email` |Le compilateur vous dit : « pas de champ emial sur le type User ». Il ajoute qu’un champ au nom proche existe, et vous montre la correction : user.email.
Voici le programme qui la provoque :
struct User { name: String, email: String,}
fn main() { let user = User { name: String::from("Ferris"), }; println!("{}", user.name); println!("{}", user.emial); // ❌ typo: the field is `email`}(Le commentaire dit : faute de frappe, le champ s’appelle email.)
Ce que ça veut dire
Section intitulée « Ce que ça veut dire »Le titre du message a deux parties, et les deux comptent :
no field `emial`: le nom écrit après le point, que Rust n’a pas trouvé.on type `User`: le type que Rust attribue àuser.
Vérifiez donc d’abord le nom, puis le type. Si le type indiqué dans le message n’est pas la struct que vous attendiez, par exemple Option<User>, &Option<User>, Vec<User> ou &T, le nom est correct : le problème, c’est que vous devez d’abord sortir la struct.
C’est comme demander « le bleu, sur la deuxième étagère » alors que vous êtes dans la mauvaise boutique. La demande est correcte, vous n’êtes simplement pas au bon endroit.
Pourquoi Rust fait ça
Section intitulée « Pourquoi Rust fait ça »En Rust, les champs d’une struct sont fixés au moment où vous l’écrivez. Le compilateur connaît la liste complète et vérifie chaque .champ à la compilation. Il n’y a pas de « on cherche à l’exécution et on obtient undefined » comme en JavaScript, ni d’AttributeError comme en Python : un mauvais nom de champ n’arrive jamais jusque dans votre programme. Voir Les structs.
Les types enveloppes comme Option<T> et Result<T, E> sont des types à part : votre struct se trouve à l’intérieur, mais ils n’ont pas ses champs. Rust ne va pas les chercher à travers l’enveloppe pour vous, parce que la valeur n’est peut-être pas là du tout. C’est à vous de dire ce qui se passe quand elle manque. Voir Option.
Le code générique suit la même règle stricte. Dans fn greet<T>(x: &T), T peut être n’importe quel type, y compris i32 ou bool, qui n’ont aucun champ. Rust vérifie une fonction générique une seule fois, pour tous les T possibles : il ne vous laisse donc utiliser que ce que vos contraintes de trait promettent. Et les traits ne peuvent promettre que des méthodes, pas des champs. Voir Les génériques et Les traits.
Comment corriger
Section intitulée « Comment corriger »Solution 1 : corriger l’orthographe
Section intitulée « Solution 1 : corriger l’orthographe »Choisissez-la quand le type indiqué dans le message est bien la struct que vous attendiez. Cherchez la ligne help: a field with a similar name exists (un champ au nom proche existe) : rustc sait généralement exactement quel champ vous vouliez :
struct User { name: String, email: String,}
fn main() { let user = User { name: String::from("Ferris"), }; println!("{}", user.name); println!("{}", user.email); // ✅ le vrai nom du champ}Sortie :
FerrisS’il n’y a pas d’indice, allez à la définition de la struct (dans la plupart des éditeurs : Ctrl+clic ou F12 sur le type) et copiez le nom depuis là. Les noms de champs tiennent compte de la casse : userName et user_name sont deux champs différents.
Solution 2 : sortir d’abord la struct de l’Option
Section intitulée « Solution 2 : sortir d’abord la struct de l’Option »Choisissez-la quand le message dit que le type est Option<User>, Option<&User> ou &Option<User>. Ça arrive tout le temps avec les fonctions qui peuvent ne rien trouver, et avec des méthodes comme .first(), .last(), .get() et .find() :
error[E0609]: no field `name` on type `Option<User>` --> src/main.rs:11:25 |11 | println!("{}", user.name); | ^^^^ unknown field |help: one of the expressions' fields has a field of the same name |11 | println!("{}", user.unwrap().name); | +++++++++L’aide indique que la valeur contenue a un champ de ce nom, et propose .unwrap(). Cette suggestion compile, mais elle fait planter le programme quand la valeur est None. Mieux vaut décider de ce qui se passe dans les deux cas, avec if let, match, ou map plus une valeur par défaut :
struct User { name: String,}
fn find_user(id: u32) -> Option<User> { if id == 1 { Some(User { name: String::from("Ferris") }) } else { None }}
fn main() { // if let : n'exécute le code que s'il y a un User if let Some(user) = find_user(1) { println!("Trouvé : {}", user.name); }
// match : traite les deux cas match find_user(2) { Some(user) => println!("Trouvé : {}", user.name), None => println!("Aucun utilisateur avec l'id 2"), }
// map + unwrap_or : prend un champ, ou une valeur par défaut let users = vec![User { name: String::from("Ada") }]; let first_name = users.first().map(|u| u.name.as_str()).unwrap_or("personne"); println!("Premier utilisateur : {first_name}");}Sortie :
Trouvé : FerrisAucun utilisateur avec l'id 2Premier utilisateur : AdaLes mêmes techniques fonctionnent sur une référence vers une Option. Si maybe_user est une &Option<User>, écrivez if let Some(user) = maybe_user, et user est alors un &User. (Pour &Option<User>, rustc ne donne aucun indice : ça vaut la peine de s’en souvenir.) Dans une fonction qui renvoie une Option, let user = find_user(1)?; marche aussi. Voir Traiter une Option avec match et if let.
Solution 3 : utiliser .0 et .1 sur les tuple structs
Section intitulée « Solution 3 : utiliser .0 et .1 sur les tuple structs »Choisissez-la quand le type est une tuple struct, comme struct Point(i32, i32);. Ses champs n’ont pas de nom, seulement une position : p.x donne donc no field `x` on type `Point` (avec l’indice p.0). Utilisez la position, ou décomposez la valeur dans des variables nommées. Si vous voulez des noms, faites-en une struct à champs nommés :
struct Point(i32, i32); // tuple struct : les champs sont .0 et .1
struct Size { width: u32, // champs nommés : on utilise .width et .height height: u32,}
fn main() { let p = Point(3, 4); println!("x = {}, y = {}", p.0, p.1);
let Point(x, y) = p; // ou on la décompose dans des variables nommées println!("distance au carré = {}", x * x + y * y);
let screen = Size { width: 1920, height: 1080 }; println!("{}x{}", screen.width, screen.height);}Sortie :
x = 3, y = 4distance au carré = 251920x1080Les tuples ordinaires fonctionnent de la même façon. pair.2 sur un tuple de deux éléments donne E0609 avec la note available fields are: `0`, `1` (les champs disponibles sont 0 et 1). Voir Accéder aux éléments par position.
Solution 4 : utiliser une méthode de trait au lieu d’un champ sur un T générique
Section intitulée « Solution 4 : utiliser une méthode de trait au lieu d’un champ sur un T générique »Choisissez-la quand le message dit on type `&T` (ou T), avec l’étiquette type parameter 'T' declared here (paramètre de type T déclaré ici). Un code comme celui-ci échoue, même si tous les types que vous passez ont un champ name :
fn greet<T>(animal: &T) { println!("Hello, {}!", animal.name); // ❌ no field `name` on type `&T`}Les traits ne peuvent pas exiger de champs, mais ils peuvent exiger une méthode d’accès (un getter). Définissez un petit trait, implémentez-le pour chaque type, et ajoutez-le comme contrainte sur T :
trait Named { fn name(&self) -> &str;}
struct Dog { name: String,}
struct Robot { model: String,}
impl Named for Dog { fn name(&self) -> &str { &self.name }}
impl Named for Robot { fn name(&self) -> &str { &self.model // le champ n'a même pas besoin d'avoir le même nom }}
fn greet<T: Named>(thing: &T) { println!("Bonjour, {} !", thing.name()); // un appel de méthode, pas un champ}
fn main() { greet(&Dog { name: String::from("Rex") }); greet(&Robot { model: String::from("R2-D2") });}Sortie :
Bonjour, Rex !Bonjour, R2-D2 !Si la fonction ne travaille jamais qu’avec un seul type, vous n’avez pas besoin de générique du tout : écrivez fn greet(animal: &Dog), et animal.name fonctionne. Voir Les traits comme paramètres de fonction.
Où vous le verrez souvent
Section intitulée « Où vous le verrez souvent »Une méthode sans (). Écrire word.len ou cart.total (où total est une méthode) ressemble beaucoup à E0609, mais rustc donne un autre code : E0615, « attempted to take value of method » (tentative de prendre la valeur d’une méthode), avec l’indice « use parentheses to call the method » (utilisez des parenthèses pour appeler la méthode). Sur un Vec, scores.len donne E0616, « field len of struct Vec is private » (le champ len de Vec est privé), parce que Vec a vraiment un champ caché de ce nom. Même correction pour les deux : word.len(), scores.len(), cart.total(). Et "hello".length donne un simple E0609 sur &str : en Rust, la méthode s’appelle len(). Voir Les chaînes de caractères.
Un Vec au lieu d’un seul élément. dogs.name sur un Vec<Dog> donne no field `name` on type `Vec<Dog>`. Une liste n’a pas de name, chacun de ses éléments en a un. Correction : choisissez-en un avec dogs[0].name, ou bouclez avec for dog in &dogs { println!("{}", dog.name); }. Voir Les vecteurs.
Un Result au lieu de la valeur. load_config().port donne no field `port` on type `Result<Config, String>`, avec le même indice .unwrap(). Correction : let config = load_config()?; dans une fonction qui renvoie un Result, ou un match. Voir L’opérateur ?.
Un champ sur un nombre. age.value sur un u32 donne encore un autre code : E0610, « u32 is a primitive type and therefore doesn’t have fields » (u32 est un type primitif et n’a donc pas de champs). Les nombres, bool et char n’ont aucun champ ; vous pensiez sans doute à une variable de type struct.
Erreurs liées
Section intitulée « Erreurs liées »- E0599 : no method named x found : la version « méthode » de cette erreur. Souvent pour les mêmes raisons : une faute de frappe, ou une
Optionqu’il faut d’abord ouvrir. - E0425 : cannot find value in this scope : la variable avant le point n’existe pas du tout, ou vous avez oublié
self.devant un champ. - E0308 : mismatched types : ce que vous verrez souvent ensuite si vous sortez la valeur d’une
Optionà un endroit et pas à un autre. E0615: attempted to take value of method: vous avez écritx.lenau lieu dex.len().E0616: field is private: le champ existe mais n’est paspub, commelensur unVec. Utilisez les méthodes du type.E0560: struct has no field named x: la même faute de frappe, mais au moment de créer la struct (User { emial: ... }).E0610: primitive type doesn't have fields:.quelque_chosesur un nombre, unboolou unchar.- Toutes les erreurs du compilateur expliquées
Petit quiz
Section intitulée « Petit quiz »Petit quiz
1. L'erreur dit qu'il n'y a pas de champ name sur le type Option<User>. Qu'est-ce que ça vous apprend ?
2. Comment lire le premier champ de struct Meters(f64); depuis une valeur m ?
3. Dans fn show<T>(x: &T), comment lire un nom depuis n'importe quel T ?
Score : 0 / 3
Pour aller plus loin
Section intitulée « Pour aller plus loin »- E0609 — Rust error codes index (en anglais) : l’explication officielle de cette erreur.
- Defining and Instantiating Structs — The Rust Book (en anglais) : les champs nommés, l’accès avec le point, et la syntaxe de mise à jour des structs.
- Tuple Structs — The Rust Book (en anglais) : les structs dont les champs s’utilisent par position, comme
.0. - The Option Enum — The Rust Book (en anglais) : pourquoi
Option<T>etTsont des types différents, et pourquoi il faut en sortir la valeur. - Traits: Defining Shared Behavior — The Rust Book (en anglais) : les méthodes de trait et les contraintes de trait, le bon outil pour le code générique.