Bỏ qua để đến nội dung

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.

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ụ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ói cannot 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 đề ghi cannot 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.

Hãy nhìn hai dấu dưới dòng 2:

  1. ^^^^^^^^^^ dưới mut scores: đây là biến mà Rust không tìm ra được kiểu.
  2. ---------- dưới Vec::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.
  3. Dòng help gợi ý let mut scores: Vec<T>. Ở đây T chỉ 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.

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.

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: 0
Năm sau bạn sẽ 43 tuổi
["đỏ", "vàng", "tím"]
None
▶ Tự chạy thử

Khô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.

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: 26
▶ Tự chạy thử

Ba 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
- Corro
▶ Tự chạy thử

Thứ 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ả:

None
Settings { volume: 0, muted: false }
Âm lượng bản dự phòng: 0
▶ Tự chạy thử

Settings::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.

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.

  • 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ột Vec<&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: u32 rồi gọi một phương thức của String như .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

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