Lỗi Rust E0689: can't call method on ambiguous numeric type (kiểu số chưa rõ)
E0689 nghĩa là: bạn gọi một phương thức trên một con số, nhưng Rust chưa biết đó là loại số nào. 2 có thể là i32, u8, u64 và nhiều kiểu khác, và mỗi kiểu có phương thức pow riêng. Bạn chỉ cần cho Rust biết bạn muốn kiểu nào, thường bằng vài ký tự thêm vào như 2_i32 hoặc let x: f64. rustc thậm chí còn chỉ cho bạn chỗ đặt chúng. (Trang này thuộc loạt bài Giải thích các lỗi trình biên dịch Rust.)
Thông báo lỗi
Phần tiêu đề “Thông báo lỗi”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`? | ++++Trình biên dịch báo không thể gọi pow trên kiểu số mơ hồ {integer}, và gợi ý chỉ định một kiểu cụ thể như i32. Đây là chương trình gây ra lỗi:
fn main() { let kb = 2.pow(10); // ❌ which kind of integer is `2`? println!("{}", kb);}Chuyện tương tự xảy ra với số thập phân. Ở đây con số được lưu vào một biến trước, nên rustc gợi ý một kiểu cho biến:
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);}Ý nghĩa
Phần tiêu đề “Ý nghĩa”{integer} và {float} là cách rustc nói “một con số mà tôi chưa chọn kiểu chính xác”. {integer} là một kiểu số nguyên nào đó (i8 đến i128, u8 đến u128, isize, usize). {float} là f32 hoặc f64.
Dấu ^^^ chỉ vào tên phương thức. Một phương thức thuộc về một kiểu: có i32::pow, u64::pow, f32::sqrt, f64::sqrt, v.v. Chúng cho ra kết quả khác nhau (một i32 không chứa nổi 2 mũ 40, một u64 thì được). Để gọi một phương thức, Rust phải biết kiểu ngay tại điểm đó trong code, và ở đây nó không biết.
Giống như hỏi người bán hàng “2 cái bao nhiêu tiền?”. Hai cái gì? Hai quả táo và hai cái TV có giá rất khác nhau. Khi bạn nói “2 quả táo”, câu trả lời trở nên dễ dàng.
Vì sao Rust làm vậy
Phần tiêu đề “Vì sao Rust làm vậy”Rust có kiểu số mặc định. Nếu không có gì khác quyết định, một số nguyên sẽ là i32 và một số thập phân sẽ là f64. Xem Số nguyên và Số thực dấu phẩy động.
Nhưng giá trị mặc định đó là phương án cuối cùng. Trước tiên Rust đọc phần còn lại của hàm để xem có gì khác chọn kiểu không, ví dụ truyền con số vào một hàm nhận u8. Chỉ đến tận cùng nó mới lùi về i32 hoặc f64. Một lời gọi phương thức không thể đợi lâu như vậy: Rust cần biết nó đang gọi pow nào trước khi kiểm tra được đối số và kiểu kết quả. Vì vậy thay vì đoán, nó dừng lại và hỏi bạn.
Điều này cũng có nghĩa là code phía sau không giúp được gì. Ngay cả let y: u64 = x.pow(10); cũng cho ra E0689, vì Rust phải chọn kiểu cho x trước khi gọi pow, còn u64 chỉ nằm ở kết quả. Điều giúp được là cho con số một kiểu trước lời gọi phương thức.
Cách sửa
Phần tiêu đề “Cách sửa”Cách sửa 1: Thêm hậu tố kiểu cho con số
Phần tiêu đề “Cách sửa 1: Thêm hậu tố kiểu cho con số”Chọn cách này khi bạn gọi phương thức trực tiếp trên một literal, như 2.pow(10) hay 2.0.sqrt(). Viết kiểu ngay sau con số. Dấu _ không bắt buộc, chỉ để dễ đọc: 2_i32 và 2i32 là như nhau.
Với số thập phân, rustc gợi ý f32, nhưng f64 là mặc định của Rust và chính xác hơn. Hãy chọn f64 trừ khi bạn có lý do để tiết kiệm bộ nhớ.
fn main() { let kb = 2_i32.pow(10); // ✅ một i32 let big = 2_u64.pow(40); // ✅ một u64: 2^40 không vừa một i32 let root = 2.0_f64.sqrt(); // ✅ một f64 let dist = (-7_i32).abs(); // ✅ cần dấu ngoặc, vì `-` đứng trước lời gọi phương thức
println!("2^10 = {}", kb); println!("2^40 = {}", big); println!("sqrt(2) = {}", root); println!("|-7| = {}", dist);}Kết quả:
2^10 = 10242^40 = 1099511627776sqrt(2) = 1.4142135623730951|-7| = 7Hãy chọn một kiểu đủ lớn cho kết quả. 2_i32.pow(40) biên dịch được, nhưng nó bị tràn số và panic trong bản build debug. Xem Tràn số nguyên.
Cách sửa 2: Cho biến một kiểu
Phần tiêu đề “Cách sửa 2: Cho biến một kiểu”Chọn cách này khi con số được lưu vào một biến trước, như let x = 2.0;. Đây là điều mà dòng help: của rustc gợi ý. Thêm : kiểu sau tên biến:
fn main() { let x: f64 = 2.0; // ✅ giờ x là một f64 println!("{:.3}", x.sqrt());
let side: f64 = 3.5; println!("diện tích = {}", side.powi(2));
let temperature: i32 = -5; println!("cách 0 độ {} độ", temperature.abs());
let base: u64 = 2; let y = base.pow(10); // y cũng là một u64 println!("{}", y);}Kết quả:
1.414diện tích = 12.25cách 0 độ 5 độ1024Một hậu tố trên giá trị cũng dùng tốt như vậy: let x = 2.0_f64;. Hãy dùng cách nào bạn thấy dễ đọc hơn. Xem Chú thích kiểu.
Cách sửa 3: Cho biến lặp hoặc đối số closure một kiểu
Phần tiêu đề “Cách sửa 3: Cho biến lặp hoặc đối số closure một kiểu”Chọn cách này khi con số đến từ một range, như for i in 1..=3 hay (1..=5).map(|n| ...). Biến i hay n có kiểu {integer}, và không có let nào để chú thích. rustc chỉ vào biến đó:
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)); | ^^^Hãy đặt hậu tố ở một đầu của range, hoặc cho đối số closure một kiểu:
fn main() { for i in 1..=3_u32 { // ✅ giờ cả range là u32 println!("{} bình phương là {}", i, i.pow(2)); }
// hoặc một hậu tố trên range... let total: u32 = (1..=5_u32).map(|n| n.pow(2)).sum(); // ...hoặc một kiểu trên đối số closure let total2: u32 = (1..=5).map(|n: u32| n.pow(2)).sum(); println!("tổng các bình phương: {} và {}", total, total2);}Kết quả:
1 bình phương là 12 bình phương là 43 bình phương là 9tổng các bình phương: 55 và 55Hãy để ý rằng chỉ let total: u32 = ... thôi là chưa đủ: rustc cần kiểu của n bên trong closure, trước khi tới .sum(). Một hậu tố trên range sửa được tất cả.
Thường gặp ở đâu
Phần tiêu đề “Thường gặp ở đâu”abs() trên một số âm. let x = -5; x.abs() cho ra can't call method `abs` on ambiguous numeric type `{integer}`. Cách sửa: let x: i32 = -5;. Cẩn thận với hậu tố trên literal: -5_i32.abs() nghĩa là -(5_i32.abs()), tức là -5. Hãy dùng dấu ngoặc: (-5_i32).abs().
Tính toán nhanh trong main hoặc trong test. let area = 3.14 * r * r; chạy tốt, vì * là toán tử, không phải phương thức. Lỗi chỉ xuất hiện khi bạn gọi một phương thức như .sqrt(), .round() hay .powi(). Cách sửa: let r: f64 = 2.0;.
Một Vec chứa các con số. Với let nums = vec![1, 2, 3];, nums.iter().map(|n| n.pow(2)) cho ra một mã khác, E0599, no method named `pow` found for reference `&{integer}`, vì ở đây n là một tham chiếu. Cách sửa vẫn vậy: vec![1_i32, 2, 3] hoặc let nums: Vec<i32> = vec![1, 2, 3];.
Lỗi liên quan
Phần tiêu đề “Lỗi liên quan”- E0282: type annotations needed: Rust hoàn toàn không suy ra được kiểu, ví dụ cho
Vec::new()hay.collect(). Cách sửa cùng ý tưởng: nêu tên kiểu. - E0610: primitive type doesn’t have fields:
.somethingkhông có()trên một{integer}, nhưx.absthay vìx.abs(). - E0599: no method named x found: kiểu đã biết, nhưng nó không có phương thức đó, hoặc (như ở trên) đó là một tham chiếu đến
{integer}. - E0308: mismatched types: lỗi bạn có thể gặp tiếp theo, nếu kiểu bạn chọn không khớp với chỗ dùng con số.
- Giải thích tất cả các lỗi trình biên dịch
Kiểm tra nhanh
Phần tiêu đề “Kiểm tra nhanh”Kiểm tra nhanh
1. Dòng nào biên dịch được?
2. Nếu không có gì khác quyết định, Rust cho 7 và 7.0 kiểu gì?
3. for i in 0..10 { println!("{}", i.pow(2)); } cho ra E0689. Cách sửa đơn giản nhất là gì?
Điểm: 0 / 3
Đọc thêm
Phần tiêu đề “Đọc thêm”- E0689 — Rust error codes index (tiếng Anh): giải thích chính thức về lỗi này.
- Data Types — The Rust Book (tiếng Anh): kiểu số nguyên và số thực, literal có hậu tố như
57u8, và giá trị mặc địnhi32/f64. - Literals — Rust by Example (tiếng Anh): cách các hậu tố như
1u32và1.0f64chọn kiểu. - Integer literal expressions — The Rust Reference (tiếng Anh): quy tắc chính xác về cách một literal nhận kiểu, kể cả việc lùi về
i32. f64::sqrt— thư viện chuẩn Rust (tiếng Anh): các phương thức của số thực (sqrt,powi,round…) cần một kiểu đã biết.