Aller au contenu

Erreur Rust E0610 : primitive type doesn't have fields (champ sur un nombre)

E0610 veut dire : vous avez écrit something.field, mais something est un simple nombre (ou un bool, ou un char), et ces types n’ont aucun champ. Presque toujours, la variable n’est pas ce que vous croyez. Peut-être avez-vous déjà sorti le nombre d’une struct, une fonction a renvoyé un nombre au lieu d’une struct, ou vous avez écrit un .0 de trop. Le message vous indique le type exact que Rust voit : vous êtes donc à un pas de la solution. (Cette page fait partie de Les erreurs du compilateur Rust expliquées.)

error[E0610]: `{integer}` is a primitive type and therefore doesn't have fields
--> src/main.rs:3:22
|
3 | println!("{}", x.value); // ❌ x is a number, it has no `value` field
| ^^^^^

Le compilateur vous dit : « {integer} est un type primitif et n’a donc pas de champs ».

Voici le programme qui la provoque :

fn main() {
let x = 5;
println!("{}", x.value); // ❌ x is a number, it has no `value` field
}

(Le commentaire dit : x est un nombre, il n’a pas de champ value.)

Le type entre backticks est ce que Rust pense de votre variable. Ici, c’est `{integer}`, ce qui veut dire « un nombre entier, mais je n’ai pas encore décidé de quelle sorte » (Rust choisit i32 plus tard si rien d’autre ne décide). Vous verrez aussi `{float}` pour les nombres à virgule, ou un type précis comme u32, usize, f64, bool ou char.

Les ^^^^^ pointent le nom après le point. Rust ne vous dit pas « value est mal écrit ». Il vous dit « un nombre ne peut avoir aucun champ, quel que soit son nom ».

La question à se poser est donc : pourquoi cette variable est-elle un nombre ? C’est comme demander « l’étiquette de prix du 5 ». Un produit a une étiquette de prix. Le nombre 5, lui, n’est qu’un nombre.

Les types primitifs comme i32, u32, f64, bool et char sont des valeurs simples intégrées au langage. Ce ne sont pas des structs : il n’y a rien à l’intérieur que l’on pourrait atteindre avec un point et un nom. Voir Les types de données.

Ils ont en revanche des méthodes : x.abs(), x.pow(2), price.round(), c.is_alphabetic(). Les méthodes s’appellent avec (). Les champs, eux, sont des données rangées dans une struct, et seuls les structs, les tuples et les unions en ont.

Comme Rust vérifie chaque .field à la compilation, cette confusion ne peut jamais devenir un plantage ou un undefined silencieux à l’exécution. Vous le savez tout de suite, à la ligne exacte.

Choisissez-la quand vous avez déjà sorti le champ de la struct plus haut. Après let count = counter.value;, count est le nombre, donc count.value demande un champ sur un u32 :

error[E0610]: `u32` is a primitive type and therefore doesn't have fields
--> src/main.rs:8:26
|
8 | println!("{}", count.value);
| ^^^^^

Utilisez soit le nombre tel quel, soit revenez à la struct et lisez-y le champ :

struct Counter {
value: u32,
}
fn main() {
let mut counter = Counter { value: 0 };
counter.value += 3;
let count = counter.value; // count est maintenant un simple u32
println!("{}", count); // ✅ on utilise le nombre lui-même
println!("{}", counter.value); // ✅ ou on lit le champ de la struct
}

Sortie :

3
3
▶ Essayez par vous-même

Vous ne savez pas quel est le type d’une variable ? Survolez-la dans votre éditeur (avec rust-analyzer, voir Les outils Rust). Ou écrivez un instant let _: () = count; : rustc répond par expected `()`, found `usize` (attendu (), trouvé usize) et vous donne ainsi le vrai type. Supprimez la ligne ensuite.

Solution 2 : renvoyer ou stocker la struct, pas le nombre

Section intitulée « Solution 2 : renvoyer ou stocker la struct, pas le nombre »

Choisissez-la quand la valeur vient d’une fonction ou d’une collection et que vous attendiez une struct. Deux cas fréquents :

  • Une fonction comme fn find_order() -> u32 renvoie l’identifiant d’une commande, puis order.total échoue parce que order est un u32.
  • Une HashMap<&str, u32> ne stocke que des scores, puis scores["ana"].score échoue parce que chaque valeur est un u32, pas un Player.

Décidez de ce que vous voulez vraiment. Si vous avez besoin de plusieurs données, changez le type de retour ou le type des valeurs pour la struct :

use std::collections::HashMap;
struct Order {
id: u32,
total: u32, // en centimes
}
// renvoie toute la commande, pas juste un nombre
fn new_order(id: u32, total: u32) -> Order {
Order { id, total }
}
struct Player {
score: u32,
level: u8,
}
fn main() {
let order = new_order(7, 1999);
println!("La commande {} coûte {} centimes", order.id, order.total);
// la map stocke des Player entiers, pas seulement leurs scores
let mut players: HashMap<&str, Player> = HashMap::new();
players.insert("ana", Player { score: 10, level: 2 });
let ana = &players["ana"];
println!("ana : score {}, niveau {}", ana.score, ana.level);
}

Sortie :

La commande 7 coûte 1999 centimes
ana : score 10, niveau 2
▶ Essayez par vous-même

Si un nombre vous suffit, gardez le u32 et retirez simplement la partie .field. Voir Les structs et Les HashMap.

Choisissez-la quand l’erreur pointe un .0 ou un .1. Les tuples et les tuple structs utilisent des positions pour leurs champs, et il est facile de descendre d’un niveau de trop. Après let x = point.0;, x est déjà le nombre, donc x.0 donne `{integer}` is a primitive type. C’est pareil avec un type enveloppe comme struct Meters(u32); : un .0 sort le u32, et un deuxième .0 échoue.

struct Meters(u32);
fn main() {
let point = (3, 4);
let x = point.0; // x est déjà le nombre 3
println!("x = {}", x); // pas x.0
let m = Meters(42);
println!("{} m", m.0); // un seul .0 sort le u32 de la tuple struct
let line = ((0, 0), (3, 4)); // tuples imbriqués : une position par niveau
println!("y de fin = {}", line.1.1);
}

Sortie :

x = 3
42 m
y de fin = 4
▶ Essayez par vous-même

Comptez les niveaux : chaque .0 ou .1 descend d’un niveau dans un tuple. Quand vous arrivez à un nombre, arrêtez-vous. Si les positions deviennent confuses, donnez-leur plutôt des noms avec une struct. Voir Accéder aux éléments par position et Tuple ou struct ?

Une méthode sans (). price.round sur un f64, ou x.abs sur un i32, ne donne pas E0610 mais E0615, « attempted to take value of method » (tentative de prendre la valeur d’une méthode), parce que ces noms sont des méthodes. Correction : price.round(), x.abs(). Une surprise : si le type est encore {integer} (comme dans let x = -5;), même x.abs donne E0610, et x.abs() donne ensuite E0689 « can’t call method abs on ambiguous numeric type {integer} » (impossible d’appeler la méthode abs sur un type numérique ambigu). Choisissez un type, let x: i32 = -5;, et x.abs() fonctionne.

Une référence vers un nombre. Si la variable est un &u32 au lieu d’un u32, le code devient E0609 : no field `value` on type `&u32`. On le rencontre souvent dans une boucle comme for (name, score) in &players, où score est un &i32, et où score.1 donne no field `1` on type `&{integer}`. Même cause, mêmes solutions : score est déjà le nombre.

Le résultat de len() ou de count(). let len = scores.len(); vous donne un usize, donc len.count échoue avec `usize` is a primitive type. Correction : utilisez len directement.

Des nombres avec une unité. Si vous voulez écrire price.cents ou distance.meters, créez une petite struct : struct Price { cents: u64 }. Alors price.cents fonctionne, et le type vous empêche de confondre des prix avec d’autres nombres. Voir Les structs.

Petit quiz

1. Après let total = cart.total; (où total est un champ u32), que donne total.amount ?

2. Que veut dire {integer} dans le message ?

3. let x = point.0; puis x.0 donne E0610. Quelle est la correction ?

Score : 0 / 3