Salta ai contenuti

Errore Rust E0689: can't call method on ambiguous numeric type (numero ambiguo)

E0689 significa: hai chiamato un metodo su un numero, ma Rust non sa ancora di che tipo di numero si tratta. 2 potrebbe essere un i32, un u8, un u64 e altro ancora, e ognuno di questi ha il suo metodo pow. Devi solo dire a Rust quale intendi, di solito con pochi caratteri in più come 2_i32 o let x: f64. rustc ti mostra persino dove metterli. (Questa pagina fa parte di Errori del compilatore Rust spiegati.)

error[E0689]: can't call method `pow` on ambiguous numeric type `{integer}`
--> src/main.rs:2:16
|
2 | let kb = 2.pow(10); // ❌ which kind of integer is `2`?
| ^^^
|
help: you must specify a concrete type for this numeric value, like `i32`
|
2 | let kb = 2_i32.pow(10); // ❌ which kind of integer is `2`?
| ++++

Il compilatore ti dice: «non si può chiamare il metodo pow sul tipo numerico ambiguo {integer}», e ti suggerisce di specificare un tipo concreto, come i32.

Ecco il programma che lo provoca:

fn main() {
let kb = 2.pow(10); // ❌ which kind of integer is `2`?
println!("{}", kb);
}

(Il commento dice: che tipo di intero è 2?)

Lo stesso succede con i decimali. Qui il numero viene prima salvato in una variabile, quindi rustc suggerisce invece un tipo per la variabile:

error[E0689]: can't call method `sqrt` on ambiguous numeric type `{float}`
--> src/main.rs:3:18
|
3 | let root = x.sqrt(); // ❌ is `x` an f32 or an f64?
| ^^^^
|
help: you must specify a type for this binding, like `f32`
|
2 | let x: f32 = 2.0;
| +++++
fn main() {
let x = 2.0;
let root = x.sqrt(); // ❌ is `x` an f32 or an f64?
println!("{}", root);
}

(Il commento dice: x è un f32 o un f64?)

{integer} e {float} sono il modo in cui rustc dice «un numero di cui non ho ancora scelto il tipo esatto». {integer} è un qualche tipo intero (da i8 a i128, da u8 a u128, isize, usize). {float} è f32 oppure f64.

I ^^^ indicano il nome del metodo. Un metodo appartiene a un tipo: esiste un i32::pow, un u64::pow, un f32::sqrt, un f64::sqrt, e così via. Danno risultati diversi (un i32 non può contenere 2 alla 40, un u64 sì). Per chiamarne uno, Rust deve conoscere il tipo in quel punto del codice, e qui non lo conosce.

È come chiedere a un commesso «quanto costano 2?». Due cosa? Due mele e due televisori costano cifre molto diverse. Appena dici «2 mele», la risposta è facile.

Rust ha dei tipi numerici predefiniti. Se nient’altro decide, un numero intero diventa un i32 e un decimale diventa un f64. Vedi Tipi di dati.

Ma quel valore predefinito è l’ultima risorsa. Rust prima legge il resto della funzione per vedere se qualcos’altro sceglie il tipo, per esempio passando il numero a una funzione che riceve un u8. Solo alla fine ripiega su i32 o f64. Una chiamata di metodo non può aspettare così tanto: Rust deve sapere quale pow sta chiamando prima di poter controllare gli argomenti e il tipo del risultato. Quindi, invece di indovinare, si ferma e te lo chiede.

Questo vuol dire anche che il codice successivo non aiuta. Persino let y: u64 = x.pow(10); dà E0689, perché Rust deve scegliere il tipo di x prima di chiamare pow, e l’u64 sta solo sul risultato. Quello che aiuta è dare un tipo al numero prima della chiamata di metodo.

Soluzione 1: aggiungi un suffisso di tipo al numero

Sezione intitolata “Soluzione 1: aggiungi un suffisso di tipo al numero”

Scegli questa strada quando chiami un metodo direttamente su un letterale, come 2.pow(10) o 2.0.sqrt(). Scrivi il tipo subito dopo il numero. Il _ è facoltativo e serve solo a rendere il tutto leggibile: 2_i32 e 2i32 sono la stessa cosa.

Per i decimali rustc suggerisce f32, ma f64 è il tipo predefinito di Rust ed è più preciso. Scegli f64 a meno che tu non abbia un motivo per risparmiare memoria.

fn main() {
let kb = 2_i32.pow(10); // ✅ un i32
let big = 2_u64.pow(40); // ✅ un u64: 2^40 non sta in un i32
let root = 2.0_f64.sqrt(); // ✅ un f64
let dist = (-7_i32).abs(); // ✅ parentesi, perché il `-` viene prima della chiamata di metodo
println!("2^10 = {}", kb);
println!("2^40 = {}", big);
println!("radice di 2 = {}", root);
println!("|-7| = {}", dist);
}

Output:

2^10 = 1024
2^40 = 1099511627776
radice di 2 = 1.4142135623730951
|-7| = 7
▶ Provalo tu

Scegli un tipo abbastanza grande per il risultato. 2_i32.pow(40) compila, ma va in overflow e fa panic in una build di debug. Vedi Overflow degli interi.

Scegli questa strada quando il numero viene prima salvato in una variabile, come let x = 2.0;. È quello che suggerisce la riga help: di rustc. Aggiungi : tipo dopo il nome della variabile:

fn main() {
let x: f64 = 2.0; // ✅ ora x è un f64
println!("{:.3}", x.sqrt());
let side: f64 = 3.5;
println!("area = {}", side.powi(2));
let temperature: i32 = -5;
println!("{} gradi da zero", temperature.abs());
let base: u64 = 2;
let y = base.pow(10); // anche y è un u64
println!("{}", y);
}

Output:

1.414
area = 12.25
5 gradi da zero
1024
▶ Provalo tu

Anche un suffisso sul valore funziona allo stesso modo: let x = 2.0_f64;. Usa quello che trovi più leggibile. Vedi Tipi di dati.

Soluzione 3: dai un tipo alla variabile del ciclo o al parametro della closure

Sezione intitolata “Soluzione 3: dai un tipo alla variabile del ciclo o al parametro della closure”

Scegli questa strada quando il numero arriva da un intervallo (range), come for i in 1..=3 o (1..=5).map(|n| ...). La variabile i o n ha tipo {integer}, e non c’è nessun let da annotare. rustc indica la variabile:

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

Metti il suffisso su un estremo dell’intervallo, oppure dai un tipo al parametro della closure:

fn main() {
for i in 1..=3_u32 { // ✅ ora tutto l'intervallo è u32
println!("{} al quadrato fa {}", i, i.pow(2));
}
// un suffisso sull'intervallo...
let total: u32 = (1..=5_u32).map(|n| n.pow(2)).sum();
// ...oppure un tipo sul parametro della closure
let total2: u32 = (1..=5).map(|n: u32| n.pow(2)).sum();
println!("somma dei quadrati: {} e {}", total, total2);
}

Output:

1 al quadrato fa 1
2 al quadrato fa 4
3 al quadrato fa 9
somma dei quadrati: 55 e 55
▶ Provalo tu

Nota che let total: u32 = ... da solo non basta: rustc ha bisogno del tipo di n dentro la closure, prima di arrivare a .sum(). Un solo suffisso sull’intervallo sistema tutto.

abs() su un numero negativo. let x = -5; x.abs() dà can't call method `abs` on ambiguous numeric type `{integer}`. Soluzione: let x: i32 = -5;. Attenzione al suffisso sul letterale: -5_i32.abs() vuol dire -(5_i32.abs()), cioè -5. Usa le parentesi: (-5_i32).abs().

Calcoli veloci in main o in un test. let area = 3.14 * r * r; funziona benissimo, perché * è un operatore, non un metodo. L’errore compare solo quando chiami un metodo come .sqrt(), .round() o .powi(). Soluzione: let r: f64 = 2.0;.

Un Vec di numeri. Con let nums = vec![1, 2, 3];, nums.iter().map(|n| n.pow(2)) dà un codice diverso, E0599, no method named `pow` found for reference `&{integer}`, perché qui n è un riferimento. Stessa soluzione: vec![1_i32, 2, 3] oppure let nums: Vec<i32> = vec![1, 2, 3];.

Verifica veloce

1. Quale riga compila?

2. Se nient'altro decide, che tipo dà Rust a 7 e a 7.0?

3. for i in 0..10 { println!("{}", i.pow(2)); } dà E0689. Qual è la soluzione più semplice?

Punteggio: 0 / 3