Lỗi Rust E0282: type annotations needed (cần chú thích kiểu)
E0282 nghĩa là Rust đã cố tự tìm ra một kiểu nhưng không đủ manh mối. Logic của bạn chẳng có gì sai. Trình biên dịch chỉ cần bạn nêu tên một kiểu, thường chỉ mất vài ký tự. Trang này cho bạn xem cách sửa trước, rồi giải thích cách Rust đoán kiểu (gọi là suy luận kiểu, type inference) hoạt động thế nào, để bạn biết khi nào cần giúp nó. Các lỗi khác trong loạt bài này nằm ở 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[E0282]: type annotations needed for `Vec<_>` --> src/main.rs:2:9 |2 | let mut scores = Vec::new(); // a Vec of... what? | ^^^^^^^^^^ ---------- type must be known at this point |help: consider giving `scores` an explicit type, where the type for type parameter `T` is specified |2 | let mut scores: Vec<T> = Vec::new(); // a Vec of... what? | ++++++++Trình biên dịch báo rằng nó cần chú thích kiểu cho Vec<_>, và gợi ý ghi rõ kiểu cho scores.
Đây là chương trình gây ra lỗi:
fn main() { let mut scores = Vec::new(); // a Vec of... what? println!("Scores recorded: {}", scores.len());}Chú thích // a Vec of... what? (một Vec chứa… cái gì?) giữ tiếng Anh vì nó xuất hiện trong thông báo lỗi.
Cùng thông báo, khác mã lỗi
Phần tiêu đề “Cùng thông báo, khác mã lỗi”Cụm từ “type annotations needed” (cần chú thích kiểu) cũng xuất hiện với ba mã lỗi khác. Chúng có cùng ý nghĩa, và các cách sửa trên trang này dùng được cho tất cả:
- E0283:
.collect()hoặc.sum()mà không có kiểu đích. rustc thêm một ghi chú nhưthe type must implement `FromIterator<&str>`hoặc`Sum<&i32>`, vì có nhiều kiểu có thể là kết quả. - E0284:
"42".parse().unwrap()mà không có kiểu đích. Ghi chú nóicannot satisfy `<_ as FromStr>::Err == _`, một cách nói đáng sợ của câu “parse thành cái gì?”. - E0790:
let settings = Default::default();. Dòng tiêu đề ghicannot call associated function on trait without specifying the corresponding `impl` type: Rust không biết bạn muốn giá trị mặc định của ai.
Bạn nhận mã nào tùy vào dòng code cụ thể, và nó có thể đổi khi bạn thêm code. Ví dụ, let age = "42".parse().unwrap(); cho ra E0284, nhưng nếu dòng tiếp theo gọi age.pow(2), nó trở thành E0282. Đừng bận tâm về con số; hãy nhìn vào biến mà rustc chỉ tới.
Ý nghĩa
Phần tiêu đề “Ý nghĩa”Hãy nhìn hai dấu dưới dòng 2:
^^^^^^^^^^dướimut scores: đây là biến mà Rust không tìm ra được kiểu.----------dướiVec::new(): “type must be known at this point” (kiểu phải được biết ở chỗ này).Vec::new()tạo một danh sách rỗng, nhưng danh sách rỗng chứa cái gì? Số? Tên? Rust cần biết để dành đúng loại chỗ chứa.- Dòng help gợi ý
let mut scores: Vec<T>. Ở đâyTchỉ là chỗ giữ chỗ. Bạn thay nó bằng một kiểu thật, nhưVec<u32>.
Hãy nghĩ tới một thùng hàng ở bưu điện mà ô “nội dung” bị để trống. Nhân viên không gửi được cho tới khi có ai ghi gì đó vào. Bạn có thể tự ghi, hoặc bỏ thứ gì đó vào thùng để nhân viên nhìn thấy.
Vì sao Rust làm vậy
Phần tiêu đề “Vì sao Rust làm vậy”Mọi giá trị trong Rust đều có một kiểu chính xác, được quyết định lúc biên dịch. Phần lớn thời gian bạn không phải viết nó ra, vì Rust đọc code của bạn và tự tìm ra. let count = 5; là một i32. let name = String::from("Ferris"); là một String. Đó là suy luận kiểu, và bài Biến cho bạn thấy thêm về nó.
Suy luận kiểu dùng manh mối từ toàn bộ hàm, không chỉ một dòng. Nếu sau đó bạn gọi scores.push(90), đó là một manh mối: Vec chứa số. Nếu bạn truyền scores vào một hàm nhận &[u32], đó còn là manh mối tốt hơn. E0282 xuất hiện khi hoàn toàn không có manh mối nào, hoặc khi Rust cần biết kiểu trước khi tới được manh mối (ví dụ, để tra một phương thức).
Một số hàm là generic ở kiểu trả về: parse, collect, sum, Default::default, Vec::new, None. Chúng có thể tạo ra nhiều kiểu khác nhau, nên Rust không thể chọn giúp bạn. Nó sẽ không đoán, vì đoán sai sẽ âm thầm thay đổi cách chương trình của bạn hoạt động. Xem Generic để biết làm sao một hàm có thể trả về nhiều kiểu.
Cách sửa
Phần tiêu đề “Cách sửa”Cách sửa 1: Chú thích kiểu cho biến
Phần tiêu đề “Cách sửa 1: Chú thích kiểu cho biến”Chọn cách này trong phần lớn trường hợp. Thêm : Kiểu sau tên biến. Nó ngắn gọn, và còn giúp người đọc code sau này hiểu rõ hơn:
fn main() { let scores: Vec<u32> = Vec::new(); let age: u32 = "42".parse().unwrap(); let words: Vec<&str> = "đỏ vàng tím".split(' ').collect(); let winner: Option<String> = None;
println!("Số điểm đã ghi: {}", scores.len()); println!("Năm sau bạn sẽ {} tuổi", age + 1); println!("{:?}", words); println!("{:?}", winner);}Kết quả:
Số điểm đã ghi: 0Năm sau bạn sẽ 43 tuổi["đỏ", "vàng", "tím"]NoneKhông phải lúc nào bạn cũng phải viết đầy đủ kiểu. let words: Vec<_> = ...collect(); nghĩa là “một Vec, còn bên trong chứa gì thì bạn tự tìm ra”. Dấu _ nhờ Rust điền phần đó. Đọc thêm về cách viết kiểu ở Kiểu dữ liệu.
Cách sửa 2: Dùng turbofish ::<>
Phần tiêu đề “Cách sửa 2: Dùng turbofish ::<>”Chọn cách này khi không có biến nào để chú thích, ví dụ ở giữa một chuỗi lời gọi iterator. Bạn đặt kiểu ngay trên lời gọi hàm bằng ::<Kiểu>. Người ta gọi ::<> là “turbofish” vì ::<> trông hơi giống một con cá:
fn main() { let input = "4 15 7"; let numbers = input .split(' ') .map(|s| s.parse::<u32>().unwrap()) .collect::<Vec<_>>(); let total = numbers.iter().sum::<u32>();
println!("{:?}", numbers); println!("Tổng: {total}");}Kết quả:
[4, 15, 7]Tổng: 26Ba lời gọi turbofish phổ biến nhất là parse::<i32>(), collect::<Vec<_>>() và sum::<i32>(). Cách sửa 1 và Cách sửa 2 đều đúng như nhau; let total: u32 = numbers.iter().sum(); làm cùng việc. Chọn cách nào đọc dễ hơn.
Cách sửa 3: Dùng giá trị, để Rust suy ra kiểu
Phần tiêu đề “Cách sửa 3: Dùng giá trị, để Rust suy ra kiểu”Chọn cách này khi lỗi xuất hiện vì code của bạn chưa viết xong. Thường thì bạn tạo một Vec rỗng, còn code đổ dữ liệu vào nó thì nằm sau. Một khi bạn dùng giá trị, Rust có manh mối và lỗi biến mất, không cần chú thích gì:
fn print_team(names: &[String]) { for name in names { println!("- {name}"); }}
fn main() { let mut team = Vec::new(); // chưa biết kiểu... tạm thời team.push(String::from("Ferris")); // giờ Rust biết: Vec<String> team.push(String::from("Corro")); print_team(&team);}Kết quả:
- Ferris- CorroThứ tự có chút quan trọng. Manh mối phải đến trước khi Rust cần biết kiểu để gọi một phương thức. Một closure như let shout = |s| s.to_uppercase(); vẫn lỗi dù bạn gọi shout("hi") ngay dòng sau, vì Rust phải biết s là gì mới tìm được .to_uppercase(). Ở đó, hãy viết kiểu ra: |s: &str| s.to_uppercase(). Xem Closure.
Cách sửa 4: Nêu tên kiểu cho None và Default::default()
Phần tiêu đề “Cách sửa 4: Nêu tên kiểu cho None và Default::default()”Chọn cách này cho những giá trị hoàn toàn không mang manh mối nào. None không nói nó sẽ chứa gì, còn Default::default() không nói bạn muốn giá trị mặc định của ai. Hãy viết kiểu vào ngay biểu thức:
#[derive(Debug, Default)]struct Settings { volume: u8, muted: bool,}
fn main() { let fastest_lap = None::<f64>; // thay vì: None let settings = Settings::default(); // thay vì: Default::default() let backup: Settings = Default::default(); // chú thích kiểu cũng được
println!("{:?}", fastest_lap); println!("{:?}", settings); println!("Âm lượng bản dự phòng: {}", backup.volume);}Kết quả:
NoneSettings { volume: 0, muted: false }Âm lượng bản dự phòng: 0Settings::default() là lựa chọn rõ ràng nhất: nó đọc lên là “Settings mặc định”. None::<f64> cũng tiện, nhưng let fastest_lap: Option<f64> = None; (Cách sửa 1) phổ biến hơn. Xem Option.
Thường gặp ở đâu
Phần tiêu đề “Thường gặp ở đâu”Chuyển văn bản thành số. let guess = line.trim().parse().unwrap(); cho ra E0284 (hoặc E0282 nếu dòng tiếp theo bạn gọi một phương thức trên guess). parse có thể tạo ra i32, u8, f64 và nhiều kiểu khác. Cách sửa: let guess: i32 = ... hoặc .parse::<i32>(). So sánh guess với một biến i32 về sau là không đủ ở đây (còn truyền nó vào một hàm nhận i32 thì đủ), nên cách sửa đơn giản nhất là nêu tên kiểu ngay ở lời gọi. E0284 giải thích chi tiết trường hợp này.
.collect(). Nó có thể tạo ra Vec, String, HashSet, HashMap… nên lúc nào nó cũng cần biết đích đến. Cách sửa: let words: Vec<_> = ...collect(); hoặc .collect::<Vec<_>>(). Xem Iterator.
.sum() và .product(). nums.iter().sum() cho ra E0283 với the type must implement `Sum<&i32>`. Cộng các i32 có thể ra một i32, nhưng các kiểu số khác cũng có thể là tổng. Cách sửa: let total: i32 = nums.iter().sum(); hoặc .sum::<i32>().
Một collection rỗng trong constructor của struct. let cache = HashMap::new(); trong một hàm new() mà bạn chưa viết xong. Cách sửa: viết đầy đủ kiểu một lần (HashMap<String, u32>), hoặc trả nó về trong một struct mà kiểu của trường đã được khai báo, đó là một manh mối Rust dùng được.
Lỗi liên quan
Phần tiêu đề “Lỗi liên quan”- E0308: mismatched types. Lỗi này xảy ra khi chú thích kiểu của bạn và giá trị không khớp nhau, như
let age: u32 = "42";. - E0277: trait bound not satisfied. Bạn đã nêu kiểu, nhưng không thể tạo ra nó theo cách đó, như
let n: Vec<i32> = "1 2".split(' ').collect();(đó là mộtVec<&str>; hãy parse từng phần tử trước). - E0599: no method found, ví dụ khi bạn chú thích
let n: u32rồi gọi một phương thức củaStringnhư.trim()trên nó. - E0004: non-exhaustive patterns, một lỗi khác mà rustc viết sẵn cách sửa cho bạn.
- Tất cả lỗi trình biên dịch: danh sách đầy đủ các trang lỗi.
Kiểm tra nhanh
Phần tiêu đề “Kiểm tra nhanh”Kiểm tra nhanh
1. Dòng nào sửa được E0282 trên let ids = Vec::new(); khi Vec này dùng để chứa các giá trị u64?
2. Vì sao let n = "7".parse().unwrap(); cần một kiểu, dù văn bản rõ ràng là một con số?
3. Dấu _ trong .collect::<Vec<_>>() nghĩa là gì?
Điểm: 0 / 3
Đọc thêm
Phần tiêu đề “Đọc thêm”- E0282 — Rust error codes index (tiếng Anh): giải thích chính thức của lỗi này, kèm thêm ví dụ.
- E0283 — Rust error codes index (tiếng Anh): biến thể bạn sẽ gặp với
collectvàsum, khi có nhiều kiểu phù hợp. - Data Types — The Rust Book (tiếng Anh): giải thích chú thích kiểu, kể cả ví dụ kinh điển
"42".parse(). - Inference — Rust by Example (tiếng Anh): một ví dụ ngắn về việc Rust suy ra kiểu của một
Vectừ code phía sau. Iterator::collect— tài liệu thư viện chuẩn (tiếng Anh): cho thấy cả kiểu viết chú thích lẫn kiểu viết turbofish.