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);}这个错误是什么意思
Section titled “这个错误是什么意思”{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 为什么要这样做
Section titled “Rust 为什么要这样做”Rust 确实有默认的数字类型。如果没有别的东西来决定,整数会成为 i32,小数会成为 f64。参见整数和浮点数(小数)。
但这个默认值是最后的退路。Rust 会先读完函数的其余部分,看看有没有别的东西决定了类型,比如把这个数字传给一个接收 u8 的函数。只有到了最后,它才会退回到 i32 或 f64。而方法调用等不了那么久:Rust 必须先知道调用的是哪一个 pow,才能检查参数和结果的类型。所以它不去猜,而是停下来问你。
这也意味着后面的代码帮不上忙。即使写成 let y: u64 = x.pow(10);,也会报 E0689,因为 Rust 必须在调用 pow 之前选定 x 的类型,而 u64 只写在结果上。真正有用的是在方法调用之前就给数字定好类型。
修复 1:给数字加上类型后缀
Section titled “修复 1:给数字加上类型后缀”当你直接在字面量上调用方法时,比如 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 = 10242^40 = 1099511627776sqrt(2) = 1.4142135623730951|-7| = 7选一个足够装下结果的类型。2_i32.pow(40) 能编译,但在 debug 构建里会溢出并 panic。参见整数溢出。
修复 2:给变量加上类型
Section titled “修复 2:给变量加上类型”当数字先存进了变量时,比如 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 的平方是 12 的平方是 43 的平方是 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];。
- E0282:type annotations needed:Rust 完全推断不出类型,比如
Vec::new()或.collect()。修复思路一样:把类型写出来。 - E0610:primitive type doesn’t have fields:在
{integer}上写了.something却没加(),比如把x.abs()写成了x.abs。 - E0599:no method named x found:类型是已知的,但它没有这个方法,或者(如上所述)它是一个指向
{integer}的引用。 - E0308:mismatched types:如果你选的类型和数字被使用的地方不匹配,接下来可能会看到它。
- 所有编译错误详解
小测验
1. 下面哪一行能通过编译?
2. 如果没有别的东西来决定,Rust 会给 7 和 7.0 什么类型?
3. for i in 0..10 { println!("{}", i.pow(2)); } 报了 E0689。最简单的修法是什么?
得分: 0 / 3
- E0689 — Rust error codes index(英文):这个错误的官方解释。
- 数据类型 — Rust 程序设计语言:整数和浮点数类型、带
57u8这类后缀的数字字面量,以及i32/f64默认类型。 - 字面量和运算符 — 通过例子学 Rust:
1u32、1.0f64这样的后缀怎样选定类型。 - Integer literal expressions — The Rust Reference(英文):字面量怎样得到类型的确切规则,包括退回到
i32的规则。 f64::sqrt— 标准库文档(英文):需要已知类型才能调用的浮点数方法(sqrt、powi、round……)。