Erreur Rust E0689 : can't call method on ambiguous numeric type (nombre ambigu)
E0689 veut dire : vous avez appelé une méthode sur un nombre, mais Rust ne sait pas encore de quelle sorte de nombre il s’agit. 2 peut être un i32, un u8, un u64 et bien d’autres, et chacun de ces types a sa propre méthode pow. Il suffit de dire à Rust lequel vous voulez, en général avec quelques caractères en plus comme 2_i32 ou let x: f64. rustc vous montre même où les mettre. (Cette page fait partie de Les erreurs du compilateur Rust expliquées.)
Le message d’erreur
Section intitulée « Le message d’erreur »error[E0689]: can't call method `pow` on ambiguous numeric type `{integer}` --> src/main.rs:2:16 |2 | let kb = 2.pow(10); // ❌ quelle sorte d'entier est `2` ? | ^^^ |help: you must specify a concrete type for this numeric value, like `i32` |2 | let kb = 2_i32.pow(10); // ❌ quelle sorte d'entier est `2` ? | ++++Le compilateur vous dit : « impossible d’appeler la méthode pow sur le type numérique ambigu {integer} ». L’aide « you must specify a concrete type for this numeric value » vous demande d’indiquer un type précis pour ce nombre, par exemple i32, et les ++++ montrent ce qu’il faut ajouter.
Voici le programme qui la provoque :
fn main() { let kb = 2.pow(10); // ❌ quelle sorte d'entier est `2` ? println!("{}", kb);}La même chose arrive avec les nombres décimaux. Ici, le nombre est d’abord rangé dans une variable, alors rustc propose plutôt un type pour la variable :
error[E0689]: can't call method `sqrt` on ambiguous numeric type `{float}` --> src/main.rs:3:18 |3 | let root = x.sqrt(); // ❌ `x` est-il un f32 ou un f64 ? | ^^^^ |help: you must specify a type for this binding, like `f32` |2 | let x: f32 = 2.0; | +++++L’aide « you must specify a type for this binding » veut dire « vous devez indiquer un type pour cette variable ».
fn main() { let x = 2.0; let root = x.sqrt(); // ❌ `x` est-il un f32 ou un f64 ? println!("{}", root);}Ce que ça veut dire
Section intitulée « Ce que ça veut dire »{integer} et {float} sont la façon dont rustc dit « un nombre dont je n’ai pas encore choisi le type exact ». {integer} est un type d’entier quelconque (de i8 à i128, de u8 à u128, isize, usize). {float} est f32 ou f64.
Les ^^^ pointent le nom de la méthode. Une méthode appartient à un type : il existe un i32::pow, un u64::pow, un f32::sqrt, un f64::sqrt, et ainsi de suite. Ils ne donnent pas les mêmes résultats (un i32 ne peut pas contenir 2 puissance 40, un u64 le peut). Pour en appeler un, Rust doit connaître le type à cet endroit du code, et ici il ne le connaît pas.
C’est comme demander à un vendeur « combien pour 2 ? ». Deux quoi ? Deux pommes et deux télévisions ne coûtent pas du tout la même chose. Dès que vous dites « 2 pommes », la réponse est facile.
Pourquoi Rust fait ça
Section intitulée « Pourquoi Rust fait ça »Rust a bien des types de nombres par défaut. Si rien d’autre ne décide, un entier devient un i32 et un nombre décimal un f64. Voir Les entiers et Les nombres à virgule flottante.
Mais ce choix par défaut est un dernier recours. Rust lit d’abord le reste de la fonction pour voir si quelque chose d’autre fixe le type, par exemple si vous passez le nombre à une fonction qui attend un u8. Ce n’est qu’à la toute fin qu’il se rabat sur i32 ou f64. Un appel de méthode ne peut pas attendre aussi longtemps : Rust doit savoir quel pow il appelle avant de pouvoir vérifier les arguments et le type du résultat. Alors, au lieu de deviner, il s’arrête et vous pose la question.
Cela veut aussi dire que le code qui vient après n’aide pas. Même let y: u64 = x.pow(10); donne E0689, car Rust doit choisir le type de x avant d’appeler pow, et le u64 ne porte que sur le résultat. Ce qui aide, c’est de donner son type au nombre avant l’appel de méthode.
Comment corriger
Section intitulée « Comment corriger »Solution 1 : ajouter un suffixe de type au nombre
Section intitulée « Solution 1 : ajouter un suffixe de type au nombre »Choisissez-la quand vous appelez une méthode directement sur un littéral, comme 2.pow(10) ou 2.0.sqrt(). Écrivez le type juste après le nombre. Le _ est facultatif et ne sert qu’à la lisibilité : 2_i32 et 2i32 sont identiques.
Pour les décimaux, rustc propose f32, mais f64 est le type par défaut de Rust et il est plus précis. Prenez f64, sauf si vous avez une raison d’économiser de la mémoire.
fn main() { let kb = 2_i32.pow(10); // ✅ un i32 let big = 2_u64.pow(40); // ✅ un u64 : 2^40 ne tient pas dans un i32 let root = 2.0_f64.sqrt(); // ✅ un f64 let dist = (-7_i32).abs(); // ✅ des parenthèses, car le `-` vient avant l'appel de méthode
println!("2^10 = {}", kb); println!("2^40 = {}", big); println!("sqrt(2) = {}", root); println!("|-7| = {}", dist);}Sortie :
2^10 = 10242^40 = 1099511627776sqrt(2) = 1.4142135623730951|-7| = 7Choisissez un type assez grand pour le résultat. 2_i32.pow(40) compile, mais il déborde et provoque un panic en mode debug. Voir Le dépassement d’entier.
Solution 2 : donner un type à la variable
Section intitulée « Solution 2 : donner un type à la variable »Choisissez-la quand le nombre est d’abord rangé dans une variable, comme let x = 2.0;. C’est ce que propose la ligne help: de rustc. Ajoutez : type après le nom de la variable :
fn main() { let x: f64 = 2.0; // ✅ maintenant x est un f64 println!("{:.3}", x.sqrt());
let side: f64 = 3.5; println!("aire = {}", side.powi(2));
let temperature: i32 = -5; println!("{} degrés d'écart avec zéro", temperature.abs());
let base: u64 = 2; let y = base.pow(10); // y est aussi un u64 println!("{}", y);}Sortie :
1.414aire = 12.255 degrés d'écart avec zéro1024Un suffixe sur la valeur marche tout aussi bien : let x = 2.0_f64;. Utilisez la forme que vous trouvez la plus lisible. Voir Les annotations de type.
Solution 3 : donner un type à la variable de boucle ou à l’argument de la closure
Section intitulée « Solution 3 : donner un type à la variable de boucle ou à l’argument de la closure »Choisissez-la quand le nombre vient d’un intervalle (range), comme for i in 1..=3 ou (1..=5).map(|n| ...). La variable i ou n est de type {integer}, et il n’y a pas de let à annoter. rustc pointe la variable :
error[E0689]: can't call method `pow` on ambiguous numeric type `{integer}` --> src/main.rs:3:26 |2 | for i in 1..=3 { | - you must specify a type for this binding, like `i32`3 | println!("{}", i.pow(2)); | ^^^Ici, l’étiquette sous i vous demande d’indiquer un type pour cette variable, par exemple i32. Mettez le suffixe sur l’une des bornes de l’intervalle, ou donnez un type à l’argument de la closure :
fn main() { for i in 1..=3_u32 { // ✅ tout l'intervalle est maintenant en u32 println!("{} au carré vaut {}", i, i.pow(2)); }
// soit un suffixe sur l'intervalle... let total: u32 = (1..=5_u32).map(|n| n.pow(2)).sum(); // ...soit un type sur l'argument de la closure let total2: u32 = (1..=5).map(|n: u32| n.pow(2)).sum(); println!("somme des carrés : {} et {}", total, total2);}Sortie :
1 au carré vaut 12 au carré vaut 43 au carré vaut 9somme des carrés : 55 et 55Remarquez que let total: u32 = ... ne suffit pas à lui seul : rustc a besoin du type de n dans la closure, avant d’arriver à .sum(). Un seul suffixe sur l’intervalle règle tout.
Où vous le verrez souvent
Section intitulée « Où vous le verrez souvent »abs() sur un nombre négatif. let x = -5; x.abs() donne can't call method `abs` on ambiguous numeric type `{integer}`. Correction : let x: i32 = -5;. Attention avec un suffixe sur le littéral : -5_i32.abs() veut dire -(5_i32.abs()), c’est-à-dire -5. Utilisez des parenthèses : (-5_i32).abs().
Un petit calcul dans main ou dans un test. let area = 3.14 * r * r; marche très bien, parce que * est un opérateur, pas une méthode. L’erreur n’apparaît que lorsque vous appelez une méthode comme .sqrt(), .round() ou .powi(). Correction : let r: f64 = 2.0;.
Un Vec de nombres. Avec let nums = vec![1, 2, 3];, nums.iter().map(|n| n.pow(2)) donne un autre code, E0599, no method named `pow` found for reference `&{integer}` (aucune méthode pow pour une référence &{integer}), car n est ici une référence. Même correction : vec![1_i32, 2, 3] ou let nums: Vec<i32> = vec![1, 2, 3];.
Erreurs liées
Section intitulée « Erreurs liées »- E0282 : type annotations needed : Rust n’arrive pas du tout à deviner un type, par exemple pour
Vec::new()ou.collect(). La correction suit la même idée : nommer le type. - E0610 : primitive type doesn’t have fields :
.quelque_chosesans()sur un{integer}, commex.absau lieu dex.abs(). - E0599 : no method named x found : le type est connu, mais il n’a pas cette méthode, ou (comme plus haut) c’est une référence vers un
{integer}. - E0308 : mismatched types : ce que vous pourriez voir ensuite, si le type choisi ne correspond pas à l’endroit où le nombre est utilisé.
- Toutes les erreurs du compilateur expliquées
Petit quiz
Section intitulée « Petit quiz »Petit quiz
1. Quelle ligne compile ?
2. Si rien d'autre ne décide, quel type Rust donne-t-il à 7 et à 7.0 ?
3. for i in 0..10 { println!("{}", i.pow(2)); } donne E0689. Quelle est la correction la plus simple ?
Score : 0 / 3
Pour aller plus loin
Section intitulée « Pour aller plus loin »- E0689 — Rust error codes index (en anglais) : l’explication officielle de cette erreur.
- Data Types — The Rust Book (en anglais) : les types entiers et décimaux, les littéraux avec suffixe comme
57u8, et les types par défauti32/f64. - Literals — Rust by Example (en anglais) : comment des suffixes comme
1u32et1.0f64choisissent un type. - Integer literal expressions — The Rust Reference (en anglais) : les règles exactes qui donnent son type à un littéral, y compris le repli sur
i32. f64::sqrt— documentation de la bibliothèque standard (en anglais) : les méthodes des décimaux (sqrt,powi,round…) qui ont besoin d’un type connu.