Aller au contenu

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

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);
}

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

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.

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 = 1024
2^40 = 1099511627776
sqrt(2) = 1.4142135623730951
|-7| = 7
▶ Essayez par vous-même

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

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.414
aire = 12.25
5 degrés d'écart avec zéro
1024
▶ Essayez par vous-même

Un 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 1
2 au carré vaut 4
3 au carré vaut 9
somme des carrés : 55 et 55
▶ Essayez par vous-même

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

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];.

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