跳转到内容

Rust 错误 E0689:can't call method on ambiguous numeric type(数值类型不明确)

E0689 的意思是:你在一个数字上调用了方法,但 Rust 还不知道它是哪种数字。 2 可以是 i32、u8、u64 等等,而它们各自都有自己的 pow 方法。你只需要告诉 Rust 你指的是哪一个,通常多写几个字符就行,比如 2_i32 或 let x: f64。rustc 甚至会告诉你该写在哪里。(本页属于 Rust 编译错误详解系列。)

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`?
| ++++

第一行的意思是“不能在不明确的数值类型 {integer} 上调用方法 pow”。help 说你必须给这个数值指定一个具体的类型,比如 i32。

产生这个错误的程序是这样的:

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

小数也一样。下面的数字先存进了变量,所以 rustc 改为建议给变量加上类型:

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

{integer} 和 {float} 是 rustc 的说法,意思是“一个我还没选定确切类型的数字”。{integer} 是某种整数类型(i8 到 i128、u8 到 u128、isize、usize)。{float} 是 f32 或 f64。

^^^ 指向方法名。方法属于某个类型:有 i32::pow、u64::pow、f32::sqrt、f64::sqrt 等等。它们给出的结果不一样(i32 装不下 2 的 40 次方,u64 装得下)。要调用其中一个,Rust 必须在代码的这个位置就知道类型,而这里它不知道。

就像你问店员“2 个多少钱?”。2 个什么?2 个苹果和 2 台电视的价钱可差远了。你一说“2 个苹果”,答案就简单了。

Rust 确实有默认的数字类型。如果没有别的东西来决定,整数会成为 i32,小数会成为 f64。参见整数和浮点数(小数)。

但这个默认值是最后的退路。Rust 会先读完函数的其余部分,看看有没有别的东西决定了类型,比如把这个数字传给一个接收 u8 的函数。只有到了最后,它才会退回到 i32 或 f64。而方法调用等不了那么久:Rust 必须先知道调用的是哪一个 pow,才能检查参数和结果的类型。所以它不去猜,而是停下来问你。

这也意味着后面的代码帮不上忙。即使写成 let y: u64 = x.pow(10);,也会报 E0689,因为 Rust 必须在调用 pow 之前选定 x 的类型,而 u64 只写在结果上。真正有用的是在方法调用之前就给数字定好类型。

当你直接在字面量上调用方法时,比如 2.pow(10) 或 2.0.sqrt(),选这个。把类型紧跟在数字后面写上。_ 可写可不写,只是为了好读:2_i32 和 2i32 是一样的。

对于小数,rustc 建议的是 f32,但 f64 是 Rust 的默认类型,而且精度更高。除非你有节省内存的理由,否则就选 f64。

fn main() {
let kb = 2_i32.pow(10); // ✅ 一个 i32
let big = 2_u64.pow(40); // ✅ 一个 u64:2^40 放不进 i32
let root = 2.0_f64.sqrt(); // ✅ 一个 f64
let dist = (-7_i32).abs(); // ✅ 要加括号,因为 `-` 在方法调用之前
println!("2^10 = {}", kb);
println!("2^40 = {}", big);
println!("sqrt(2) = {}", root);
println!("|-7| = {}", dist);
}

输出:

2^10 = 1024
2^40 = 1099511627776
sqrt(2) = 1.4142135623730951
|-7| = 7
▶ 动手试一试

选一个足够装下结果的类型。2_i32.pow(40) 能编译,但在 debug 构建里会溢出并 panic。参见整数溢出。

当数字先存进了变量时,比如 let x = 2.0;,选这个。这也正是 rustc 的 help: 那一行的建议。在变量名后面加上 : 类型:

fn main() {
let x: f64 = 2.0; // ✅ 现在 x 是 f64 了
println!("{:.3}", x.sqrt());
let side: f64 = 3.5;
println!("面积 = {}", side.powi(2));
let temperature: i32 = -5;
println!("与 0 度相差 {} 度", temperature.abs());
let base: u64 = 2;
let y = base.pow(10); // y 也是 u64
println!("{}", y);
}

输出:

1.414
面积 = 12.25
与 0 度相差 5 度
1024
▶ 动手试一试

在值上加后缀也一样有效:let x = 2.0_f64;。哪种读起来顺手就用哪种。参见类型注解。

修复 3:给循环变量或闭包参数加上类型

Section titled “修复 3:给循环变量或闭包参数加上类型”

当数字来自一个区间时,比如 for i in 1..=3 或 (1..=5).map(|n| ...),选这个。变量 i 或 n 的类型是 {integer},而且没有 let 可以加注解。rustc 会指向这个变量:

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

在区间的一端加上后缀,或者给闭包参数加上类型:

fn main() {
for i in 1..=3_u32 { // ✅ 现在整个区间都是 u32
println!("{} 的平方是 {}", i, i.pow(2));
}
// 要么给区间加后缀……
let total: u32 = (1..=5_u32).map(|n| n.pow(2)).sum();
// ……要么给闭包参数加类型
let total2: u32 = (1..=5).map(|n: u32| n.pow(2)).sum();
println!("平方和:{} 和 {}", total, total2);
}

输出:

1 的平方是 1
2 的平方是 4
3 的平方是 9
平方和:55 和 55
▶ 动手试一试

注意,只写 let total: u32 = ... 是不够的:rustc 需要在走到 .sum() 之前,就知道闭包里面 n 的类型。在区间上加一个后缀,就全解决了。

在负数上调用 abs()。 let x = -5; x.abs() 会报 can't call method `abs` on ambiguous numeric type `{integer}`。修复:let x: i32 = -5;。在字面量上加后缀时要小心:-5_i32.abs() 的意思是 -(5_i32.abs()),结果是 -5。要加括号:(-5_i32).abs()。

在 main 或测试里随手算点东西。 let area = 3.14 * r * r; 完全没问题,因为 * 是运算符,不是方法。只有调用 .sqrt()、.round() 或 .powi() 这样的方法时,错误才会出现。修复:let r: f64 = 2.0;。

一个装数字的 Vec。 对于 let nums = vec![1, 2, 3];,nums.iter().map(|n| n.pow(2)) 会报另一个错误代码 E0599:no method named `pow` found for reference `&{integer}`(在引用 &{integer} 上找不到名为 pow 的方法),因为这里的 n 是一个引用。修法一样:vec![1_i32, 2, 3] 或 let nums: Vec<i32> = vec![1, 2, 3];。

小测验

1. 下面哪一行能通过编译?

2. 如果没有别的东西来决定,Rust 会给 7 和 7.0 什么类型?

3. for i in 0..10 { println!("{}", i.pow(2)); } 报了 E0689。最简单的修法是什么?

得分: 0 / 3