Aller au contenu

Erreur Rust E0061 : this function takes N arguments but M were supplied

E0061 veut dire que vous avez appelé une fonction avec le mauvais nombre d’arguments : trop peu ou trop. C’est l’une des erreurs les plus sympathiques de Rust, parce que rustc vous dit exactement quel argument manque ou est en trop, et de quel type il devrait être. On la voit souvent chez les personnes qui viennent de Python, JavaScript ou C#, où les arguments par défaut et la surcharge sont monnaie courante. Rust n’a ni l’un ni l’autre, et cette page vous montre quoi faire à la place. Pour les autres erreurs de cette série, voir Les erreurs du compilateur Rust.

error[E0061]: this function takes 2 arguments but 1 argument was supplied
--> src/main.rs:6:5
|
6 | greet("Ferris"); // ❌ greet veut deux arguments
| ^^^^^---------- argument #2 of type `&str` is missing
|
note: function defined here
--> src/main.rs:1:4
|
1 | fn greet(name: &str, greeting: &str) {
| ^^^^^ --------------
help: provide the argument
|
6 | greet("Ferris", /* &str */); // ❌ greet veut deux arguments
| ++++++++++++

En français : « cette fonction prend 2 arguments, mais 1 seul a été fourni ». L’étiquette précise que l’argument n° 2, de type &str, manque.

Voici le programme qui la provoque :

fn greet(name: &str, greeting: &str) {
println!("{greeting}, {name}!");
}
fn main() {
greet("Ferris"); // ❌ greet veut deux arguments
}

Trop d’arguments donne le même code, avec une étiquette « unexpected argument » (argument inattendu) et une aide « remove the extra argument » (retirez l’argument en trop) :

error[E0061]: this function takes 2 arguments but 3 arguments were supplied
--> src/main.rs:6:13
|
6 | let a = area(3, 4, 5);
| ^^^^ - unexpected argument #3 of type `{integer}`

Pour une méthode appelée avec un point, la première ligne dit « this method takes… » à la place. Pour une tuple struct comme Rgb(255, 0), elle dit « this struct takes 3 arguments ». C’est à chaque fois la même erreur.

Le message a trois parties, et chacune vous aide :

  1. La première ligne compte : la fonction veut 2 arguments, vous en avez donné 1.
  2. L’étiquette sous votre appel nomme le problème : « argument #2 of type &str is missing » (l’argument n° 2, de type &str, manque).
  3. La note: renvoie à la définition de la fonction et souligne le paramètre que vous avez oublié, greeting: &str.

La ligne help: montre où va l’argument. /* &str */ n’est qu’un repère qui vous indique le type ; c’est un commentaire, donc le coller tel quel ne compilera pas. Mettez une vraie valeur à sa place, comme "Bonjour".

C’est comme une machine à café qui demande à la fois une tasse et une pièce. Mettre seulement la pièce ne vous donne pas un demi-café. La machine ne démarre pas, c’est tout.

En Rust, la signature d’une fonction est un contrat précis : ces paramètres, dans cet ordre, avec ces types. Chaque appel doit le respecter. Il n’y a pas d’arguments par défaut (greeting = "Hello"), pas de surcharge (deux fonctions nommées greet avec des paramètres différents), ni de fonctions qui acceptent « n’importe quel nombre » d’arguments. Essayer d’écrire une valeur par défaut donne error: parameter defaults are not supported, et une deuxième fonction greet donne E0428, « the name greet is defined multiple times » (le nom greet est défini plusieurs fois).

Ça a l’air strict, mais c’est payant. Quand vous lisez greet(name, greeting), vous savez exactement quelle fonction s’exécute et ce qu’elle reçoit. Quand vous ajoutez un paramètre à une fonction, le compilateur liste tous les appels à mettre à jour : aucun n’est oublié. (Les choses comme println!, qui acceptent vraiment n’importe quel nombre de valeurs, sont des macros : c’est ce que veut dire le !. Voir Les macros.)

Le fonctionnement des paramètres est expliqué dans Les fonctions.

Solution 1 : passer exactement ce que demande la signature

Section intitulée « Solution 1 : passer exactement ce que demande la signature »

Choisissez cette solution presque à chaque fois. Ouvrez l’emplacement indiqué par la note:, lisez la liste des paramètres, et passez une valeur par paramètre, dans l’ordre :

fn greet(name: &str, greeting: &str) {
println!("{greeting}, {name} !");
}
fn main() {
greet("Ferris", "Bonjour");
greet("Corro", "Bonne journée");
}

Sortie :

Bonjour, Ferris !
Bonne journée, Corro !
▶ Essayez par vous-même

Si vous venez de modifier la fonction et que tous les appels sont maintenant cassés, c’est le compilateur qui vous aide : corrigez chaque appel qu’il liste, ou demandez-vous si le nouveau paramètre devrait vraiment être facultatif (solution 3).

Choisissez cette solution quand la fonction a un paramètre self. Une méthode s’appelle normalement sous la forme valeur.methode(args). La valeur avant le point remplit self, vous ne la comptez donc pas. Si vous écrivez Type::methode(args) à la place, rien ne remplit self :

error[E0061]: this function takes 2 arguments but 1 argument was supplied
--> src/main.rs:13:5
|
13 | Account::deposit(50);
| ^^^^^^^^^^^^^^^^ -- argument #1 of type `&mut Account` is missing
|
help: provide the argument
|
13 | Account::deposit(/* &mut Account */, 50);
| +++++++++++++++++++

Ici, c’est l’argument n° 1, &mut Account, qui manque : c’est la place de self.

Remarquez le décompte. Appelée sous la forme Account::deposit(...), rustc dit que la fonction prend 2 arguments, parce que &mut self compte. Appelée sous la forme acc.deposit(), il dit que la méthode prend 1 argument, parce que le point fournit déjà self. Les deux formes fonctionnent dès que tous les arguments sont là :

struct Account {
balance: i64,
}
impl Account {
fn deposit(&mut self, amount: i64) {
self.balance += amount;
}
}
fn main() {
let mut acc = Account { balance: 0 };
acc.deposit(50); // la façon habituelle
Account::deposit(&mut acc, 25); // le même appel, écrit en entier
println!("Solde : {}", acc.balance);
}

Sortie :

Solde : 75
▶ Essayez par vous-même

Utilisez la forme avec le point dans le code de tous les jours. Type::methode(&x, a) est utile quand vous devez passer la méthode elle-même quelque part, comme team.iter().map(Player::label) (exemple). Si une fonction n’a pas du tout de self (comme new), elle ne peut s’appeler que sous la forme Type::fonction(...). Voir Les structs et E0424.

Solution 3 : utiliser Option<T> pour un argument qui n’est pas toujours nécessaire

Section intitulée « Solution 3 : utiliser Option<T> pour un argument qui n’est pas toujours nécessaire »

Choisissez cette solution quand un argument a le plus souvent une valeur par défaut raisonnable. Faites-en une Option, passez None pour « utiliser la valeur par défaut », et complétez-la avec unwrap_or :

fn connect(host: &str, port: u16, timeout_secs: Option<u64>) {
let timeout = timeout_secs.unwrap_or(30);
println!("Connexion à {host}:{port} (délai {timeout} s)");
}
fn main() {
connect("localhost", 8080, None);
connect("example.com", 443, Some(5));
}

Sortie :

Connexion à localhost:8080 (délai 30 s)
Connexion à example.com:443 (délai 5 s)
▶ Essayez par vous-même

Soyons honnêtes : les appelants écrivent quand même l’argument, None. Rust préfère ça, parce que l’appel montre qu’une valeur par défaut est utilisée. Si vous préférez ne pas l’écrire du tout, créez plutôt deux fonctions aux noms clairs, comme connect(host, port) qui appelle connect_with_timeout(host, port, 30). C’est la réponse habituelle de Rust à la surcharge. Voir Option.

Solution 4 : utiliser une struct de configuration avec Default

Section intitulée « Solution 4 : utiliser une struct de configuration avec Default »

Choisissez cette solution quand une fonction a beaucoup de réglages et que la plupart des appelants n’en changent qu’un ou deux. Rangez les réglages dans une struct, implémentez Default, et laissez les appelants ne remplir que ce dont ils ont besoin avec ..Default::default() :

struct ServerConfig {
host: String,
port: u16,
timeout_secs: u64,
verbose: bool,
}
impl Default for ServerConfig {
fn default() -> Self {
Self {
host: String::from("localhost"),
port: 8080,
timeout_secs: 30,
verbose: false,
}
}
}
fn start(config: ServerConfig) {
println!(
"En écoute sur {}:{} (délai {} s, verbose : {})",
config.host, config.port, config.timeout_secs, config.verbose
);
}
fn main() {
start(ServerConfig::default());
start(ServerConfig { port: 3000, verbose: true, ..Default::default() });
}

Sortie :

En écoute sur localhost:8080 (délai 30 s, verbose : false)
En écoute sur localhost:3000 (délai 30 s, verbose : true)
▶ Essayez par vous-même

Vous obtenez aussi une sorte d’arguments nommés : port: 3000 dit ce que signifie le nombre. Si la valeur par défaut de chaque champ est celle de son type (0, false, chaîne vide), #[derive(Default)] écrit l’impl pour vous. Pour des API plus grandes, le builder pattern (patron « monteur »), par exemple Server::builder().port(3000).build(), est l’étape suivante. Voir Les structs et Les design patterns.

Après avoir ajouté un paramètre. Vous donnez un deuxième paramètre à fn save(path: &str), et tous les anciens appels save("notes.txt") cassent avec E0061. Correction : mettez à jour chaque appel que le compilateur liste, ou utilisez la solution 3 ou 4 si la nouvelle valeur est presque toujours la même.

Les méthodes de la bibliothèque standard. names.push("Cy", "Di") donne « this method takes 1 argument but 2 arguments were supplied ». push ajoute un seul élément. Correction : appelez-la deux fois, ou utilisez names.extend(["Cy", "Di"]). Voir Les vecteurs.

Les tuple structs. struct Rgb(u8, u8, u8); puis Rgb(255, 0) donne « this struct takes 3 arguments but 2 arguments were supplied ». Correction : donnez tous les champs, Rgb(255, 0, 0).

Les habitudes venues d’autres langages. Le greet("Ferris", greeting="Hi") de Python n’est pas de la syntaxe Rust. Écrire greet(greeting = "Hi") ne nomme pas un argument ; rustc le lit comme une affectation à une variable appelée greeting, et signale en plus E0425. Correction : passez les valeurs par position, ou utilisez une struct de configuration (solution 4).

Les closures. Appeler add(1) alors que add vaut |a: i32, b: i32| a + b donne le même message, mais sous le code E0057 (la version closure). La correction est la même : passez les deux valeurs.

  • E0308 : le nombre d’arguments est bon, mais l’un d’eux a le mauvais type, souvent parce que deux arguments ont été inversés.
  • E0424 : self utilisé dans une fonction sans paramètre self, l’autre face de la différence entre méthode et fonction associée.
  • E0599 : aucune méthode de ce nom n’existe, par exemple quand on appelle une fonction associée comme new avec un point.
  • E0425 : impossible de trouver une valeur ; peut apparaître à côté de E0061 quand vous essayez les arguments nommés.
  • Toutes les erreurs du compilateur : la liste complète des pages d’erreurs. Liées aussi : E0057 (closure appelée avec le mauvais nombre d’arguments) et E0428 (une fonction définie deux fois, en essayant de la surcharger), qui n’a pas de page à elle.

Petit quiz

1. fn area(w: u32, h: u32) -> u32 est appelée avec area(5). Que signale rustc ?

2. Une méthode est déclarée fn deposit(&mut self, amount: i64). Quel appel est correct ?

3. Vous voulez un argument timeout dont la plupart des appelants se moquent. Quelle est une approche courante en Rust ?

Score : 0 / 3