Lỗi Rust E0283: type annotations needed (cần chú thích kiểu cho sum, collect, into)
E0283 nghĩa là Rust biết giá trị của bạn phải làm được gì, nhưng có nhiều kiểu làm được điều đó, và Rust sẽ không tự chọn giúp bạn. Bạn thường gặp nó ngay sau .sum(), .collect() hoặc .into(). Đây là một trong những lỗi dễ sửa nhất: bạn chỉ cần nêu tên kiểu bạn muốn là xong. Các lỗi khác trong loạt bài này có ở 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[E0283]: type annotations needed --> src/main.rs:3:9 |3 | let total = prices.iter().sum(); | ^^^^^ --- type must be known at this point | = note: the type must implement `Sum<&i32>` = help: the following types implement trait `Sum<A>`: `Duration` implements `Sum<&'a Duration>` `Duration` implements `Sum` `Option<T>` implements `Sum<Option<U>>` `Result<T, E>` implements `Sum<Result<U, E>>` `Saturating<u128>` implements `Sum<&'a Saturating<u128>>` `Saturating<u128>` implements `Sum` `Saturating<u16>` implements `Sum<&'a Saturating<u16>>` `Saturating<u16>` implements `Sum` and 88 othersnote: required by a bound in `std::iter::Iterator::sum`help: consider giving `total` an explicit type |3 | let total: /* Type */ = prices.iter().sum(); | ++++++++++++Trình biên dịch nói cần chú thích kiểu cho total, vì có rất nhiều kiểu cài đặt Sum, và gợi ý ghi kiểu rõ ràng cho biến. Đây là chương trình gây ra lỗi:
fn main() { let prices = vec![250, 120, 99]; // prices in cents let total = prices.iter().sum(); println!("Total: {total} cents");}.collect() và .into() cho ra cùng lỗi này với một ghi chú khác. Với let words = text.split(' ').collect(); ghi chú là the type must implement `FromIterator<&str>`. Với let name = "Ferris".into(); nó là the type must implement `From<&str>`.
Ý nghĩa
Phần tiêu đề “Ý nghĩa”Hãy đọc thông báo từ trên xuống dưới:
^^^^^dướitotal: đây là biến mà Rust không quyết định được kiểu.---dướisum: “type must be known at this point” (kiểu phải được biết tại điểm này).sumcó thể trả về nhiều kiểu khác nhau, nên Rust cần câu trả lời ở đây.- Ghi chú:
the type must implement `Sum<&i32>`. Đây là quy tắc mà Rust tìm ra: “totalphải là một kiểu có thể tạo ra bằng cách cộng dồn các giá trị&i32”. - Danh sách help: các kiểu biết cách trở thành “tổng của thứ gì đó”. Có rất nhiều: mọi kiểu số,
Duration, thậm chí cảOptionvàResult. Rust hiển thị tám cái và nói “and 88 others” (và 88 kiểu khác). Đó là điều người ta muốn nói khi bảo multiple impls satisfy this bound: nhiều hơn một kiểu cóimpltuân theo quy tắc.
Hãy tưởng tượng bạn gọi “một thứ gì đó để uống” ở quán cà phê. Đó là một quy tắc thật (phải uống được), nhưng trà, nước ép và nước lọc đều thỏa mãn. Người phục vụ không thể mang gì ra cho đến khi bạn chọn. E0283 là Rust đang cầm khay đứng chờ câu trả lời của bạn.
Vì sao Rust làm vậy
Phần tiêu đề “Vì sao Rust làm vậy”sum, collect và into là generic theo kiểu trả về. Kiểu mà bạn yêu cầu sẽ quyết định hàm làm gì. Trait (Sum, FromIterator, From) chỉ là quy tắc. Vì vậy khi không có gì cho Rust biết kiểu, nó có quy tắc nhưng không có một câu trả lời duy nhất.
Và câu trả lời rất quan trọng. Collect cùng các từ vào một Vec<&str> giữ lại các từ trùng, còn collect vào một HashSet<&str> thì loại bỏ chúng. Cộng dồn vào một u8 sẽ tràn số ở 255, còn u64 thì không. Nếu Rust tự chọn kiểu giúp bạn, một sửa đổi nhỏ ở đâu đó có thể âm thầm thay đổi hành vi của chương trình. Rust thích hỏi một lần hơn.
Cách Rust tự suy ra kiểu (suy luận kiểu, type inference) được giải thích ở trang E0282, nên không nhắc lại ở đây. Về các trait đứng sau những phương thức này, xem Trait và Generic.
E0282, E0283, E0284: một vấn đề, ba mã lỗi
Phần tiêu đề “E0282, E0283, E0284: một vấn đề, ba mã lỗi”Cả ba đều nói “type annotations needed” (cần chú thích kiểu), và với bạn chúng có cùng ý nghĩa: hãy viết kiểu ra. Mã lỗi chỉ cho biết Rust đang làm gì khi bị kẹt:
- E0282: Rust hoàn toàn không có manh mối về kiểu, hoặc nó cần kiểu trước khi gặp được manh mối (ví dụ để tra một phương thức như
.len()). - E0283 (trang này): Rust có một manh mối dưới dạng quy tắc trait (
must implement `Sum<&i32>`), nhưng nhiều kiểu tuân theo quy tắc đó. - E0284: quy tắc nói về một kiểu thuộc về kiểu chưa biết, như kiểu lỗi của
parse(cannot satisfy `<_ as FromStr>::Err == _`). Rust không kiểm tra được cho đến khi biết kiểu chính.
Mã lỗi thậm chí có thể thay đổi khi bạn đang gõ. let words = text.split(' ').collect(); theo sau là println!("{:?}", words) là E0283. Đổi dòng tiếp theo thành words.len() thì nó thành E0282, vì giờ Rust cần kiểu để tìm phương thức. Các cách sửa dưới đây dùng được cho cả ba.
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”Hầu hết thời gian hãy chọn cách này. Thêm : Type sau tên biến. Nó ngắn gọn, và cho người đọc tiếp theo biết chính xác bạn đang tạo ra thứ gì:
use std::collections::HashSet;
fn main() { let prices = vec![250, 120, 99]; let total: u32 = prices.iter().sum();
let tags = "rust,web,rust,cli"; let list: Vec<&str> = tags.split(',').collect(); let unique: HashSet<&str> = tags.split(',').collect();
println!("Tổng: {total} xu"); println!("{:?}", list); println!("Số thẻ khác nhau: {}", unique.len());}Kết quả:
Tổng: 469 xu["rust", "web", "rust", "cli"]Số thẻ khác nhau: 3Cùng một collect(), hai kết quả khác nhau: kiểu bạn viết thật sự là mệnh lệnh. Bạn có thể để phần bên trong cho Rust tự lo bằng _, như let list: Vec<_> = ....
Cách sửa 2: Dùng turbofish trên sum và collect
Phần tiêu đề “Cách sửa 2: Dùng turbofish trên sum và collect”Chọn cách này khi không có biến nào để chú thích: bên trong một println!, giữa một chuỗi iterator, hoặc làm đối số hàm. Đặt kiểu lên lời gọi bằng ::<Type> (cú pháp “turbofish”):
fn main() { let prices = vec![250, 120, 99]; println!("Tổng: {} xu", prices.iter().sum::<u32>());
let line = "tea,cake,soup"; let items = line.split(',').collect::<Vec<_>>(); let initials = line.split(',').map(|s| &s[..1]).collect::<String>();
println!("{:?}", items); println!("Chữ cái đầu: {initials}");}Kết quả:
Tổng: 469 xu["tea", "cake", "soup"]Chữ cái đầu: tcsCác món ở đây được giữ tiếng Anh vì &s[..1] cắt theo byte, và chữ tiếng Việt có dấu chiếm nhiều byte. .product::<u64>() hoạt động theo cùng cách. Cách sửa 1 và Cách sửa 2 đều đúng như nhau, hãy chọn cách dễ đọc hơn.
Cách sửa 3: Với .into(), hãy nêu tên kiểu đích (turbofish không dùng được)
Phần tiêu đề “Cách sửa 3: Với .into(), hãy nêu tên kiểu đích (turbofish không dùng được)”Chọn cách này cho .into() và .try_into(). Rất dễ bị cám dỗ viết "Ferris".into::<String>(), nhưng đó lại là một lỗi khác, vì tham số kiểu thuộc về trait Into, không thuộc về phương thức:
error[E0107]: method takes 0 generic arguments but 1 generic argument was supplied --> src/main.rs:2:25 |2 | let name = "Ferris".into::<String>(); | ^^^^ expected 0 generic argumentsThay vào đó, hãy chú thích kiểu cho biến, hoặc gọi thẳng From trên kiểu đích, cách này đọc cũng rất dễ:
fn main() { let name: String = "Ferris".into(); let city = String::from("Lisbon");
let small: u8 = 200; let big: u64 = small.into(); let also_big = u64::from(small);
let level: i64 = 42; let byte: u8 = level.try_into().unwrap(); let too_big = u8::try_from(300_i64);
println!("{name} sống ở {city}"); println!("{big} {also_big} {byte}"); println!("300 có vừa một u8 không? {}", too_big.is_ok());}Kết quả:
Ferris sống ở Lisbon200 200 42300 có vừa một u8 không? falseString::from(...) và u64::from(...) nêu kiểu đích ngay đầu dòng, nên nhiều người thích chúng hơn .into() khi không có manh mối nào khác. Xem Chuyển đổi kiểu.
Cách sửa 4: Dùng giá trị ở nơi kiểu của nó đã cố định
Phần tiêu đề “Cách sửa 4: Dùng giá trị ở nơi kiểu của nó đã cố định”Chọn cách này khi giá trị dù sao cũng được đưa tới một nơi có kiểu đã biết, như một tham số hàm hay một field của struct. Rust đọc cả hàm, nên lần dùng sau đó là đủ:
fn print_receipt(total: u32, items: Vec<&str>) { println!("{} món, {} xu", items.len(), total);}
fn main() { let prices = vec![250, 120, 99]; let total = prices.iter().sum(); // u32, nhờ print_receipt let items = "tea,cake,soup".split(',').collect(); // Vec<&str>, cùng lý do print_receipt(total, items);}Kết quả:
3 món, 469 xuKhông phải lần dùng nào cũng tính. if prices.iter().sum() > 400 vẫn là E0283, vì > có thể so sánh nhiều cặp kiểu. Truyền giá trị vào một tham số generic như impl Display cũng không giúp được, vì rất nhiều kiểu là Display. Lần dùng đó phải nêu đúng một kiểu cụ thể.
Thường gặp ở đâu
Phần tiêu đề “Thường gặp ở đâu”.collect() thẳng vào println!. println!("{:?}", line.split(',').collect()); cho ra E0283, và rustc tự gợi ý cách sửa: .collect::<Vec<_>>().
.try_into().unwrap(). let small = big.try_into().unwrap(); cho ra E0283 với the type must implement `TryFrom<i64>`. Cách sửa: let small: u8 = big.try_into().unwrap(); hoặc u8::try_from(big).unwrap().
Default::default() truyền vào một hàm generic. Với fn show(value: impl Debug), lời gọi show(Default::default()) là E0283: Rust không biết bạn muốn giá trị mặc định của kiểu nào. Cách sửa: show(Settings::default()) hoặc show(u32::default()). Đứng riêng một dòng, let s = Default::default(); lại cho ra E0790; xem Default.
Hàm generic của chính bạn. Nếu một tham số kiểu chỉ xuất hiện ở kiểu trả về, như fn make<T: Default>() -> T, thì let v = make(); là E0283. Cách sửa: make::<Settings>() hoặc let v: Settings = make();.
Lỗi liên quan
Phần tiêu đề “Lỗi liên quan”- E0282: cần chú thích kiểu, khi Rust hoàn toàn không có manh mối về kiểu (hoặc cần nó để gọi phương thức).
- E0284: cần chú thích kiểu với
cannot satisfy `<_ as FromStr>::Err == _`, gần như luôn đến từ.parse(). - E0790:
Default::default()(hoặc một hàm trait khác) được gọi mà không nói rõ kiểu nào. - E0277: 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();. - E0107: sai số đối số generic, chính là thứ
.into::<String>()đưa cho bạn. - Tất cả cá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. Ghi chú the type must implement Sum<&i32> của E0283 nói gì với bạn?
2. Dòng nào sửa được let name = "Ferris".into();?
3. Vì sao collect cùng các từ vào Vec<&str> và HashSet<&str> lại cho kết quả khác nhau?
Điểm: 0 / 3
Đọc thêm
Phần tiêu đề “Đọc thêm”- E0283 — Rust error codes index (tiếng Anh): giải thích chính thức, với ví dụ kinh điển: các
chars()bị đảo ngược nên thànhVec<char>hayString? - Processing a Series of Items with Iterators — The Rust Book (tiếng Anh): cách
sumvàcollecttiêu thụ một iterator, với các ví dụ có chú thích kiểu. Iterator::collect— tài liệu thư viện chuẩn (tiếng Anh): cho thấy kiểu chú thích và kiểu turbofish đặt cạnh nhau.Sum— tài liệu thư viện chuẩn (tiếng Anh): trait đứng sau.sum(), và danh sách đầy đủ các kiểu cài đặt nó.Into— tài liệu thư viện chuẩn (tiếng Anh): vì sao bạn cài đặtFromrồi gọi.into(), và kiểu đích được chọn như thế nào.