Aller au contenu

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.)

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"),
email: String::from("[email protected]"),
};
println!("{}", user.name);
println!("{}", user.emial); // ❌ typo: the field is `email`
}

(Le commentaire dit : faute de frappe, le champ s’appelle email.)

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.

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.

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"),
email: String::from("[email protected]"),
};
println!("{}", user.name);
println!("{}", user.email); // ✅ le vrai nom du champ
}

Sortie :

▶ Essayez par vous-même

S’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é : Ferris
Aucun utilisateur avec l'id 2
Premier utilisateur : Ada
▶ Essayez par vous-même

Les 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 = 4
distance au carré = 25
1920x1080
▶ Essayez par vous-même

Les 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 !
▶ Essayez par vous-même

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.

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.

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