Lỗi Rust E0284: type annotations needed (cần chú thích kiểu cho parse())
E0284 gần như luôn có một ý nghĩa duy nhất: bạn gọi .parse() và Rust không biết phải chuyển văn bản thành kiểu gì. Ghi chú cannot satisfy `<_ as FromStr>::Err == _` trông đáng sợ, nhưng cách sửa thường chỉ vài ký tự, như : u32 hoặc ::<u32>. Trang này đưa cách sửa trước, rồi giải mã ghi chú đó để nó hết đáng sợ. 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[E0284]: type annotations needed --> src/main.rs:2:9 |2 | let age = "42".parse().unwrap(); | ^^^ ----- type must be known at this point | = note: cannot satisfy `<_ as FromStr>::Err == _`help: consider giving `age` an explicit type |2 | let age: /* Type */ = "42".parse().unwrap(); | ++++++++++++Trình biên dịch nói cần chú thích kiểu, vì nó phải biết kiểu tại chỗ gọi parse, và gợi ý ghi kiểu rõ ràng cho age. Đây là chương trình gây ra lỗi:
fn main() { let age = "42".parse().unwrap(); println!("Next year you'll be {}", age + 1);}Bạn sẽ gặp cùng ghi chú này với .expect(...) thay cho .unwrap(), với match "42".parse() { ... }, và với .parse() bên trong một closure. Khi .parse() không được lưu vào biến, rustc chỉ thẳng vào parse và gợi ý parse::</* Type */>().
Ý nghĩa
Phần tiêu đề “Ý nghĩa”Các dấu đánh dấu là phần dễ:
^^^dướiage: biến chưa biết kiểu.-----dướiparse: “type must be known at this point” (kiểu phải được biết tại điểm này). Đây là nơi Rust bị kẹt.- Phần help: “consider giving
agean explicit type” (hãy choagemột kiểu rõ ràng). Thay/* Type */bằng một kiểu thật, nhưu32.
Giờ đến ghi chú, từng mảnh một. Trong thông báo của trình biên dịch, _ nghĩa là “một kiểu tôi chưa biết”:
| Mảnh | Nói đơn giản |
|---|---|
<_ as FromStr> |
“kiểu mà bạn đang parse thành (tôi chưa biết)” |
::Err |
“…và kiểu lỗi đi kèm với nó” |
== _ |
“phải bằng một kiểu khác mà tôi cũng chưa biết” |
Vậy cả ghi chú nói rằng: “Tôi thậm chí không xác định được parse có thể trả về loại lỗi gì, vì tôi không biết bạn đang parse thành kiểu gì.”
Hãy nghĩ đến việc đưa cho một phiên dịch viên một lá thư và nói “dịch cái này đi”. Sang tiếng Pháp? Tiếng Nhật? Cho đến khi bạn nói rõ, họ không thể bắt đầu, và thậm chí không thể cho bạn biết họ có thể gặp những lỗi gì.
Vì sao Rust làm vậy
Phần tiêu đề “Vì sao Rust làm vậy”Đây là chữ ký của parse, đã được đơn giản hóa một chút:
fn parse<F: FromStr>(&self) -> Result<F, F::Err>F là kiểu bạn muốn nhận về, và F::Err là kiểu lỗi của nó. Mỗi kiểu đích mang theo kiểu lỗi riêng: parse một i32 có thể thất bại với ParseIntError, một f64 với ParseFloatError, một bool với ParseBoolError. Rust cần F thì mới biết parse trả về cái gì.
Lựa chọn này cũng thay đổi điều xảy ra lúc chạy. "42" parse tốt thành u8, i32 hay f64. Nhưng "300" thất bại khi là u8 (quá lớn), còn "4.5" thất bại khi là i32 (không phải số nguyên). Rust sẽ không đoán, vì đoán sai sẽ thành một bug chỉ lộ ra với một số dữ liệu nhập nhất định. Bài Chuyển đổi kiểu cho thấy FromStr hoạt động thế nào, và cách thêm nó cho kiểu của riêng bạn.
Cách Rust tự suy ra kiểu được giải thích ở trang E0282. Vì sao có ba mã khác nhau (E0282, E0283, E0284) cho “type annotations needed” được giải thích ở trang E0283. Nói ngắn gọn: E0284 là biến thể mà quy tắc bị kẹt nói về một kiểu thuộc về kiểu chưa biết (ở đây là F::Err). Với bạn, cách sửa vẫn vậy: nêu tên 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”Hãy chọn cách này trong hầu hết trường hợp. Đó là phong cách mà Rust Book dùng, và nó ghi lại luôn văn bản nên chứa gì:
fn main() { let age: u32 = "42".parse().unwrap(); let price: f64 = "4.99".parse().unwrap(); let port: u16 = "8080".parse().expect("cổng phải là một con số");
println!("Năm sau bạn sẽ {} tuổi", age + 1); println!("Hai ly cà phê: {:.2}", price * 2.0); println!("Đang lắng nghe ở cổng {port}");}Kết quả:
Năm sau bạn sẽ 43 tuổiHai ly cà phê: 9.98Đang lắng nghe ở cổng 8080Hãy chọn kiểu khớp với ý nghĩa của văn bản: u16 cho cổng, u8 cho phần trăm, f64 cho giá tiền. Một kiểu quá nhỏ sẽ biến dữ liệu hợp lệ thành lỗi, nên khi phân vân, hãy chọn kiểu rộng hơn như i64 hay u32.
Cách sửa 2: Dùng turbofish parse::<T>()
Phần tiêu đề “Cách sửa 2: Dùng turbofish parse::<T>()”Chọn cách này khi không có biến nào để chú thích: bên trong closure, giữa một chuỗi iterator, hoặc trong một biểu thức như ... * 2:
fn main() { let line = "3 7 5"; let total = line .split(' ') .map(|s| s.parse::<u32>().unwrap()) .sum::<u32>(); let doubled = "21".parse::<i32>().unwrap() * 2;
println!("Tổng: {total}"); println!("Gấp đôi: {doubled}");}Kết quả:
Tổng: 15Gấp đôi: 42Hãy đặt turbofish lên chính parse. Chỉ chú thích kết quả cuối cùng thường là chưa đủ bên trong một chuỗi; xem mục đầu tiên trong phần “Thường gặp ở đâu” bên dưới.
Cách sửa 3: Nêu tên kiểu và xử lý lỗi đàng hoàng
Phần tiêu đề “Cách sửa 3: Nêu tên kiểu và xử lý lỗi đàng hoàng”Chọn cách này cho dữ liệu nhập thật (file, đối số dòng lệnh, form trên web), nơi .unwrap() sẽ làm sập chương trình khi gặp văn bản sai. Nêu kiểu trên parse và dùng match trên Result. Hãy để ý kiểu quyết định dữ liệu nào thành công:
fn main() { for input in ["42", "300", "abc"] { match input.parse::<u8>() { Ok(n) => println!("{input}: được {n}"), Err(e) => println!("{input}: {e}"), } }
let rate: Result<f64, _> = "0.25".parse(); println!("{:?}", rate);}Kết quả:
42: được 42300: number too large to fit in target typeabc: invalid digit found in stringOk(0.25)Các thông báo lỗi là của chính Rust nên vẫn bằng tiếng Anh: “số quá lớn để vừa kiểu đích” và “có chữ số không hợp lệ trong chuỗi”. Trong Result<f64, _>, bạn chỉ nêu kiểu giá trị. Dấu _ để Rust tự điền kiểu lỗi (ParseFloatError), chính là phần mà ghi chú đang hỏi. Xem thêm ở Result.
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ị đã parse được đưa vào một field của struct hoặc một tham số hàm có kiểu đã biết. Rust đọc cả hàm, nên lần dùng sau đó trả lời được câu hỏi:
struct Config { port: u16, workers: u8,}
fn greet(age: u32) { println!("Chào mừng! Bạn {age} tuổi.");}
fn main() { let port = "8080".parse().unwrap(); // u16, từ Config let workers = "4".parse().unwrap(); // u8, từ Config let config = Config { port, workers }; println!("Cổng {} với {} worker", config.port, config.workers);
let age = "42".parse().unwrap(); // u32, từ greet greet(age);}Kết quả:
Cổng 8080 với 4 workerChào mừng! Bạn 42 tuổi.Không phải lần dùng nào cũng tính, nên đây mới là Cách sửa 4. Phép toán như age + 1 và so sánh như age >= limit vẫn cho ra E0284, kể cả khi limit là u32: toán tử làm việc giữa nhiều cặp kiểu, nên chúng không nêu đúng một kiểu cụ thể. Còn gọi một phương thức, như age.pow(2), sẽ biến lỗi thành E0282. Khi phân vân, hãy dùng Cách sửa 1.
Thường gặp ở đâu
Phần tiêu đề “Thường gặp ở đâu”Parse bên trong map, rồi sum(). let total: u32 = line.split(' ').map(|s| s.parse().unwrap()).sum(); vẫn là E0284, dù total đã có kiểu. Một u32 có thể là tổng của các giá trị u32 hoặc của các giá trị &u32, nên chú thích không cho Rust biết từng phần tử là gì. Cách sửa: s.parse::<u32>(). (Còn collect vào Vec<i32> thì lại được, vì một Vec<i32> chỉ có thể chứa i32.)
Collect kết quả parse vào một Result. let nums: Result<Vec<_>, _> = text.split(' ').map(|x| x.parse()).collect(); là E0284: dấu _ trong Vec<_> để ngỏ kiểu phần tử. Cách sửa: Result<Vec<i32>, _>. Mẫu tiện lợi này dừng ở phần tử sai đầu tiên và trả cho bạn lỗi của nó.
Đọc dữ liệu người dùng nhập. let guess = line.trim().parse().unwrap(); trong một trò chơi đoán số hay một công cụ dòng lệnh. Cách sửa: let guess: u32 = ..., đúng như trong Rust Book.
Với ? bạn nhận một lỗi lạ hơn. let port = text.parse()?; không cho ra E0284. Ở edition 2021, Rust lùi về kiểu () và báo E0277: the trait bound `(): FromStr` is not satisfied (edition 2024 ghi ! thay cho ()). Cách sửa vẫn vậy: let port: u16 = text.parse()?;.
Lỗi liên quan
Phần tiêu đề “Lỗi liên quan”- E0282: cần chú thích kiểu, khi hoàn toàn không có manh mối (hoặc cần kiểu cho một lời gọi phương thức như
age.pow(2)). - E0283: cần chú thích kiểu sau
.sum(),.collect()hoặc.into(), khi nhiều kiểu cùng phù hợp. Trang đó cũng giải thích vì sao có ba mã khác nhau. - E0790:
FromStr::from_str("42")hoặcDefault::default()được gọi mà không nói rõ kiểu nào. - E0277: thứ
.parse()?đưa cho bạn khi không có kiểu, và lỗi bạn nhận khi kiểu đã chọn không parse được từ văn bản. - E0599: không tìm thấy phương thức, ví dụ gọi
.parse()trên thứ không phải văn bản, như một biếnu32(hãy chuyển đổi bằngu8::try_from(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. cannot satisfy <_ as FromStr>::Err == _ nói đơn giản nghĩa là gì?
2. Dòng nào sửa được let port = "8080".parse().unwrap();?
3. let total: u32 = line.split(' ').map(|s| s.parse().unwrap()).sum(); vẫn cho ra E0284. Nên đặt kiểu ở đâu?
Điểm: 0 / 3
Đọc thêm
Phần tiêu đề “Đọc thêm”- E0284 — Rust error codes index (tiếng Anh): giải thích chính thức về lỗi này.
str::parse— tài liệu thư viện chuẩn (tiếng Anh): chữ ký của phương thức, với cả kiểu chú thích lẫn kiểu turbofish.FromStr— tài liệu thư viện chuẩn (tiếng Anh): trait đứng sauparse, gồm kiểuErrcủa nó và cách cài đặt.- Programming a Guessing Game — The Rust Book (tiếng Anh): nơi
let guess: u32 = guess.trim().parse()xuất hiện lần đầu, được giải thích từng bước. - Data Types — The Rust Book (tiếng Anh): vì sao
"42".parse()cần chú thích kiểu, kèm lỗi thật của trình biên dịch.