Aller au contenu

Erreur Rust E0424 : expected value, found module `self` (pas de paramètre self)

E0424 veut dire que vous avez écrit self dans une fonction qui n’a pas de paramètre self : Rust n’a donc aucun « soi » vers lequel pointer. C’est une étourderie très courante, surtout si vous venez de Java, de C# ou de JavaScript, où this est toujours là. La correction tient presque toujours en un mot : ajoutez &self à la liste des paramètres de la fonction. Cette page fait partie du guide des erreurs du compilateur.

error[E0424]: expected value, found module `self`
--> src/main.rs:7:32
|
6 | fn describe() {
| -------- this function doesn't have a `self` parameter
7 | println!("Player: {}", self.name); // ❌ which player?
| ^^^^ `self` value is a keyword only available in methods with a `self` parameter
|
help: add a `self` receiver parameter to make the associated `fn` a method
|
6 | fn describe(&self) {
| +++++
error[E0599]: no method named `describe` found for struct `Player` in the current scope
--> src/main.rs:13:10
|
13 | hero.describe();
| ^^^^^^^^ this is an associated function, not a method

Le compilateur vous dit : « valeur attendue, module self trouvé ». Il ajoute que self n’est disponible que dans les méthodes qui ont un paramètre self, et il vous propose d’écrire fn describe(&self).

Voici le programme qui la provoque :

struct Player {
name: String,
}
impl Player {
fn describe() {
println!("Player: {}", self.name); // ❌ which player?
}
}
fn main() {
let hero = Player { name: String::from("Ferris") };
hero.describe();
}

(Le commentaire demande : « quel joueur ? ».)

La deuxième erreur, E0599, est une conséquence de la première. Corrigez E0424 et elle disparaît aussi.

Regardez les deux étiquettes que rustc place sur votre code :

  1. Ligne 6 : fn describe() a des parenthèses vides. rustc note « this function doesn’t have a self parameter » (cette fonction n’a pas de paramètre self).
  2. Ligne 7 : self.name demande le nom du « joueur sur lequel on a appelé la fonction ». Mais describe n’a reçu aucun joueur, donc self ne veut rien dire ici.

Une fonction dans un bloc impl sans paramètre self est une fonction associée. Elle appartient au type (Player::describe()), pas à une valeur précise. Une fonction avec un paramètre self est une méthode, et seules les méthodes peuvent utiliser self.

Imaginez un formulaire qui dit « écrivez votre nom ici ». Ça n’a de sens que si quelqu’un tient le formulaire. describe() est un formulaire que personne ne tient.

Pourquoi le message parle-t-il de « found module self » ? En Rust, self a un deuxième sens : le module courant, comme dans use self::helpers;. Quand il n’y a pas de valeur self, rustc se rabat sur ce sens-là, puis se plaint qu’un module n’est pas une valeur. Vous pouvez ignorer le mot « module » : le vrai problème, c’est le paramètre manquant.

En Rust, rien n’est caché. Java et C# donnent à chaque méthode non statique un this invisible. Rust vous demande d’écrire self comme premier paramètre, et de dire aussi comment la méthode le reçoit : pour simplement regarder, pour modifier, ou pour en prendre possession. Ce choix fait partie des règles du borrowing (emprunt), il doit donc être visible.

Cela dit clairement à toute personne qui lit votre code, et au compilateur, ce qu’une méthode fait de sa valeur. fn summary(&self) promet de ne rien changer. fn add(&mut self, ...) prévient qu’elle modifie des choses. fn checkout(self) consomme la valeur. Le compilateur vérifie ensuite ces promesses pour vous. Les méthodes et les blocs impl sont expliqués dans Les structs.

Choisissez-la quand la fonction doit travailler sur une valeur qui existe déjà. C’est la bonne solution la plupart du temps. Ajoutez le paramètre suggéré par rustc et appelez la méthode avec un point :

struct Player {
name: String,
}
impl Player {
fn describe(&self) {
println!("Joueur : {}", self.name);
}
}
fn main() {
let hero = Player { name: String::from("Ferris") };
hero.describe();
}

Sortie :

Joueur : Ferris
▶ Essayez par vous-même

rustc suggère toujours &self, mais il en existe trois sortes. Choisissez celle qui correspond à ce que fait la méthode :

Paramètre La méthode… Noms typiques
&self lit seulement la valeur summary, len, is_empty, describe
&mut self modifie la valeur add, push, reset, level_up
self consomme la valeur (en prend l’ownership, la possession) into_..., finish, build, checkout

Voici les trois sur un même type :

struct Order {
items: Vec<String>,
}
impl Order {
fn summary(&self) -> String {
format!("{} article(s) : {}", self.items.len(), self.items.join(", "))
}
fn add(&mut self, item: &str) {
self.items.push(item.to_string());
}
fn checkout(self) -> Vec<String> {
self.items
}
}
fn main() {
let mut order = Order { items: Vec::new() };
order.add("café");
order.add("croissant");
println!("{}", order.summary());
let packed = order.checkout(); // order est consommée ici
println!("Emballé : {:?}", packed);
}

Sortie :

2 article(s) : café, croissant
Emballé : ["café", "croissant"]
▶ Essayez par vous-même

Si vous choisissez &self alors que la méthode modifie un champ, le compilateur vous le dit tout de suite (E0594 « cannot assign to self.count, which is behind a & reference », ou E0596 pour des appels comme push). Passez alors à &mut self. Dans le doute, commencez par &self ; self est le cas rare.

Solution 2 : l’appeler sous la forme Type::method(&value)

Section intitulée « Solution 2 : l’appeler sous la forme Type::method(&value) »

Une fois qu’une fonction a un paramètre self, vous pouvez l’appeler de deux façons. hero.label() et Player::label(&hero) veulent dire exactement la même chose : la valeur que vous mettriez avant le point devient le premier argument. Vous écrirez la forme avec le point la plupart du temps. La forme Type:: est pratique quand vous voulez passer la méthode elle-même à quelque chose comme map :

struct Player {
name: String,
level: u32,
}
impl Player {
fn label(&self) -> String {
format!("{} (niveau {})", self.name, self.level)
}
}
fn main() {
let hero = Player { name: String::from("Ferris"), level: 3 };
println!("{}", hero.label());
println!("{}", Player::label(&hero)); // même appel
let team = vec![hero, Player { name: String::from("Corro"), level: 5 }];
let labels: Vec<String> = team.iter().map(Player::label).collect();
println!("{:?}", labels);
}

Sortie :

Ferris (niveau 3)
Ferris (niveau 3)
["Ferris (niveau 3)", "Corro (niveau 5)"]
▶ Essayez par vous-même

Ce qui ne marche pas, c’est Player::label() sans valeur : une méthode a toujours besoin de son self. L’oublier donne E0061, « this function takes 1 argument but 0 arguments were supplied » (cette fonction prend 1 argument, mais 0 ont été fournis).

Solution 3 : passer en paramètre ce dont la fonction a besoin

Section intitulée « Solution 3 : passer en paramètre ce dont la fonction a besoin »

Choisissez-la quand il n’y a pas encore de valeur, ou quand la fonction n’est pas du tout dans un bloc impl.

Le cas classique, c’est un constructeur. new crée la valeur, il n’y a donc aucun self à lire. Donnez à new les données de départ sous forme de paramètres normaux, et construisez la struct avec Self { ... } :

struct Counter {
count: u32,
step: u32,
}
impl Counter {
fn new(step: u32) -> Self {
Self { count: 0, step }
}
fn tick(&mut self) {
self.count += self.step;
}
}
fn main() {
let mut counter = Counter::new(5);
counter.tick();
counter.tick();
println!("Compteur : {}", counter.count);
}

Sortie :

Compteur : 10
▶ Essayez par vous-même

Attention aux majuscules : Self (S majuscule) est le type, donc Self { ... } construit une nouvelle valeur. self (s minuscule) est la valeur sur laquelle une méthode a été appelée.

La même idée corrige une fonction libre, c’est-à-dire en dehors de tout bloc impl. Là, rustc formule les choses un peu autrement : « this function can’t have a self parameter » (cette fonction ne peut pas avoir de paramètre self). Passez plutôt la struct par référence :

struct Cart {
items: Vec<u32>,
}
fn total(cart: &Cart) -> u32 {
cart.items.iter().sum()
}
// à appeler ainsi : total(&cart)

Ou déplacez total dans impl Cart et donnez-lui &self, comme dans la solution 1. Les deux conviennent ; une méthode se lit plus naturellement quand la fonction concerne clairement un panier.

Dans new. Écrire self.count = 0; dans fn new() -> Self donne E0424, et la suggestion de rustc, fn new(&self) -> Self, n’aurait aucun sens (il vous faudrait un compteur pour créer un compteur). Correction : construisez la valeur avec Self { count: 0 }, comme dans la solution 3.

Dans une closure, à l’intérieur d’une fonction associée. Ce n’est pas la closure qui pose problème, c’est la fonction qui l’entoure :

error[E0424]: expected value, found module `self`
--> src/main.rs:8:35
|
7 | fn sale_prices(prices: &[u32]) -> Vec<u32> {
| ----------- this function doesn't have a `self` parameter
8 | prices.iter().map(|p| p - self.discount).collect()
| ^^^^ `self` value is a keyword only available in methods with a `self` parameter

Correction : faites-en une méthode, fn sale_prices(&self, prices: &[u32]), ou passez le nombre en paramètre, fn sale_prices(prices: &[u32], discount: u32). Voir Les closures.

Dans main ou une fonction utilitaire. self ne fonctionne jamais en dehors d’un bloc impl. Correction : passez la struct en paramètre (total(&cart)), ou appelez une méthode dessus (cart.total()).

Quand vous venez des méthodes « static » de C# ou de Java. Une fonction associée sans self est la version Rust d’une méthode statique. Si vous voulez la version liée à une instance, vous devez ajouter vous-même le paramètre self. Voir Rust pour les développeurs C#.

  • E0425 : l’étourderie inverse. Vous êtes dans une méthode, mais vous avez écrit count au lieu de self.count.
  • E0599 : « no method named … found » (aucune méthode de ce nom). Souvent affichée juste après E0424, parce qu’une fonction sans self ne peut pas être appelée avec un point.
  • E0061 : mauvais nombre d’arguments, par exemple quand vous appelez Player::label() sans la valeur.
  • E0596 : vous avez ajouté &self, mais la méthode modifie un champ, elle a donc besoin de &mut self. Affecter un champ de cette façon donne E0594.
  • Toutes les erreurs du compilateur : la liste complète des pages d’erreurs.

Petit quiz

1. Dans impl Player, fn heal() { self.hp += 10; } donne E0424. Quelle est la meilleure correction ?

2. Pourquoi fn new() -> Self ne peut-elle pas utiliser self.name ?

3. fn label(&self) -> String est définie sur Player. Quel appel est identique à hero.label() ?

Score : 0 / 3