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

Lỗi Rust E0107: wrong number of generic arguments (sai số đối số generic)

E0107 nghĩa là số lượng kiểu bên trong <...> không khớp với số mà kiểu hoặc hàm đó cần. Result cần hai (Result<T, E>), HashMap cần hai (HashMap<K, V>), Option và Vec cần một. Đưa thiếu một hoặc thừa một là rustc sẽ nói kiểu như enum takes 2 generic arguments but 1 generic argument was supplied (enum nhận 2 đối số generic nhưng chỉ được đưa 1), với “struct”, “enum”, “type alias”, “function” hoặc “method” ở đầu câu. Đây là một trong những lỗi dễ chịu nhất: rustc cho bạn biết cả hai con số, và cách sửa là thêm hoặc bớt một kiểu. Trang này thuộc loạt bài về lỗi trình biên dịch.

error[E0107]: enum takes 2 generic arguments but 1 generic argument was supplied
--> src/main.rs:1:29
|
1 | fn parse_age(text: &str) -> Result<u32> { // ❌ `Result` needs two types
| ^^^^^^ --- supplied 1 generic argument
| |
| expected 2 generic arguments
|
help: add missing generic argument
|
1 | fn parse_age(text: &str) -> Result<u32, E> { // ❌ `Result` needs two types
| +++

Thông báo của trình biên dịch luôn bằng tiếng Anh. Đây là chương trình gây ra lỗi đó (chuỗi và chú thích được giữ bằng tiếng Anh vì code ví dụ lỗi được giữ nguyên như bản gốc):

fn parse_age(text: &str) -> Result<u32> { // ❌ `Result` needs two types
text.trim().parse()
}
fn main() {
for input in ["42", "forty"] {
match parse_age(input) {
Ok(age) => println!("age: {}", age),
Err(e) => println!("error: {}", e),
}
}
}

rustc chỉ vào dòng 1 hai lần:

  1. Dấu ^^^^^^ dưới Result: “expected 2 generic arguments” (cần 2 đối số generic). Result là một enum có hai tham số kiểu: T cho giá trị khi thành công và E cho lỗi.
  2. Dấu --- dưới u32: “supplied 1 generic argument” (được đưa 1 đối số generic). Bạn chỉ đưa T.
  3. Dòng help: thêm một chữ E tạm thời. Đó là gợi ý về chỗ đặt kiểu, không phải bản thân kiểu: bạn thay E bằng kiểu lỗi thật.

Đối số generic giống như các ô trống trên một tờ khai. Tờ khai Result có hai ô, “nếu thành công:” và “nếu thất bại:”. Chỉ điền một ô thì nhân viên sẽ trả lại cho bạn.

Lỗi này rất hay gặp nếu bạn từng đọc code viết Result<u32> với chỉ một kiểu. Đoạn code đó không sai: nó dùng một alias kiểu (type alias) như io::Result<T> hoặc anyhow::Result<T>, thứ tự điền sẵn kiểu lỗi cho bạn. Result thông thường từ prelude thì không có lối tắt đó.

Một kiểu generic chưa phải kiểu hoàn chỉnh cho tới khi mọi tham số đều có giá trị. Result<u32, E> với E chưa biết không cho biết giá trị lớn cỡ nào hay lỗi trông ra sao, nên Rust không thể biên dịch code dùng nó. Rust có thể thử đoán kiểu còn thiếu từ phần thân hàm, nhưng nó cố ý không bao giờ suy luận kiểu trong chữ ký hàm. Chữ ký là bản hợp đồng mà code khác dựa vào, nên phải được viết ra đầy đủ.

Một số tham số có giá trị mặc định. HashMap<K, V, S = RandomState> có tham số thứ ba cho bộ băm (hasher), đó là lý do rustc nói “at least 2” (ít nhất 2) với HashMap. Giá trị mặc định chỉ có thể điền từ cuối danh sách, và chỉ ở chỗ tác giả của kiểu đã thêm vào. Xem Generic và Result.

Phần lớn trường hợp, hãy chọn cách này. Tìm xem tham số còn thiếu nên là gì rồi viết nó ra. Với parse(), kiểu lỗi khi một số không phân tích được là std::num::ParseIntError:

use std::num::ParseIntError;
fn parse_age(text: &str) -> Result<u32, ParseIntError> {
text.trim().parse()
}
fn main() {
for input in ["42", "bốn mươi"] {
match parse_age(input) {
Ok(age) => println!("tuổi: {}", age),
Err(e) => println!("lỗi: {}", e),
}
}
}

Kết quả:

tuổi: 42
lỗi: invalid digit found in string
▶ Tự chạy thử

“invalid digit found in string” (có chữ số không hợp lệ trong chuỗi) là thông báo của chính thư viện chuẩn, nên nó luôn bằng tiếng Anh. Không chắc đó là kiểu lỗi nào? Hãy xem tài liệu của hàm mà bạn trả về lỗi của nó. Cách sửa tương tự dùng được cho HashMap<String>: nói rõ giá trị là gì, như HashMap<String, u32>. Xem HashMap và Lỗi tùy chỉnh.

Cách sửa 2: Dùng alias một kiểu mà bạn định viết, như io::Result

Phần tiêu đề “Cách sửa 2: Dùng alias một kiểu mà bạn định viết, như io::Result”

Chọn cách này khi bạn làm việc với file, mạng hoặc stdin, hoặc khi bạn thấy Result<T> trong code của người khác. std::io::Result<T> là cách viết tắt của Result<T, std::io::Error>. Import module và viết io::Result:

use std::fs;
use std::io;
fn read_config(path: &str) -> io::Result<String> {
fs::read_to_string(path)
}
fn main() {
match read_config("missing-config.toml") {
Ok(text) => println!("{}", text),
Err(e) => println!("không đọc được cấu hình: {}", e),
}
}

Kết quả:

không đọc được cấu hình: No such file or directory (os error 2)
▶ Tự chạy thử

Phần “No such file or directory” (không có file hoặc thư mục đó) do hệ điều hành trả về, nên vẫn bằng tiếng Anh. Các ứng dụng dùng crate anyhow cũng viết anyhow::Result<T> theo cách tương tự. Sai sót ngược lại cũng là E0107: io::Result<String, io::Error> cho ra type alias takes 1 generic argument but 2 generic arguments were supplied, vì alias đã chứa sẵn kiểu lỗi. Hãy bỏ kiểu thứ hai. Xem Đọc ghi file và anyhow và thiserror.

Cách sửa 3: Cho hàm là generic, hoặc ghi rõ các kiểu

Phần tiêu đề “Cách sửa 3: Cho hàm là generic, hoặc ghi rõ các kiểu”

Chọn cách này khi kiểu đó là struct generic của chính bạn và bạn chỉ viết mỗi tên của nó. rustc nói missing generics for struct `Pair` (thiếu generic cho struct Pair):

error[E0107]: missing generics for struct `Pair`
--> src/main.rs:6:13
|
6 | fn show(p: &Pair) { // ❌ which `K` and `V`?
| ^^^^ expected 2 generic arguments
|
note: struct defined here, with 2 generic parameters: `K`, `V`
--> src/main.rs:1:8
|
1 | struct Pair<K, V> {
| ^^^^ - -
help: add missing generic arguments
|
6 | fn show(p: &Pair<K, V>) { // ❌ which `K` and `V`?
| ++++++

Chương trình bị lỗi:

struct Pair<K, V> {
key: K,
value: V,
}
fn show(p: &Pair) { // ❌ which `K` and `V`?
println!("{} = {}", p.key, p.value);
}
fn main() {
show(&Pair { key: "volume", value: 7 });
show(&Pair { key: "theme", value: "dark" });
}

Phần help: viết Pair<K, V>, nhưng K và V chưa tồn tại trong show, nên bạn còn phải khai báo chúng. Vì show được gọi với hai loại Pair khác nhau, hãy cho nó là generic, kèm các ràng buộc cần để in ra:

use std::fmt::Display;
struct Pair<K, V> {
key: K,
value: V,
}
fn show<K: Display, V: Display>(p: &Pair<K, V>) {
println!("{} = {}", p.key, p.value);
}
fn main() {
show(&Pair { key: "âm lượng", value: 7 });
show(&Pair { key: "giao diện", value: "tối" });
}

Kết quả:

âm lượng = 7
giao diện = tối
▶ Tự chạy thử

Nếu hàm chỉ bao giờ xử lý một loại, hãy ghi rõ các kiểu: fn show(p: &Pair<&str, u32>). impl Pair { ... } cũng vậy, nó cần được viết là impl<K, V> Pair<K, V> { ... }. Xem Generic.

Chọn cách này khi rustc nói “but 2 generic arguments were supplied” và muốn ít hơn. Nó hay xảy ra với turbofish ::<> khi gọi hàm:

error[E0107]: function takes 1 generic argument but 2 generic arguments were supplied
--> src/main.rs:13:20
|
13 | println!("{}", largest::<i32, i32>(&scores)); // ❌ one type too many
| ^^^^^^^ ----- help: remove the unnecessary generic argument
| |
| expected 1 generic argument
|
note: function defined here, with 1 generic parameter: `T`
--> src/main.rs:1:4
|
1 | fn largest<T: PartialOrd + Copy>(items: &[T]) -> T {
| ^^^^^^^ -

Chương trình bị lỗi:

fn largest<T: PartialOrd + Copy>(items: &[T]) -> T {
let mut best = items[0];
for &item in items {
if item > best {
best = item;
}
}
best
}
fn main() {
let scores = [72, 95, 88];
println!("{}", largest::<i32, i32>(&scores)); // ❌ one type too many
}

largest có một tham số, T, nên nó nhận một kiểu. Hãy viết largest::<i32>(...), hoặc bỏ hẳn turbofish, vì Rust tự suy ra T từ đối số:

fn largest<T: PartialOrd + Copy>(items: &[T]) -> T {
let mut best = items[0];
for &item in items {
if item > best {
best = item;
}
}
best
}
fn main() {
let scores = [72, 95, 88];
println!("{}", largest(&scores));
println!("{}", largest::<f64>(&[1.5, 0.5]));
}

Kết quả:

95
1.5
▶ Tự chạy thử

Dòng note: là dòng hữu ích nhất ở đây: nó cho thấy định nghĩa và liệt kê các tham số, nên bạn có thể tự đếm.

HashMap<String> chỉ có khóa. rustc nói struct takes at least 2 generic arguments but 1 generic argument was supplied. Cách sửa: HashMap<String, u32>, hoặc viết HashMap::new() và để Rust suy ra cả hai từ lần insert đầu tiên. Xem HashMap.

Struct của bạn có hai tham số kiểu, nhưng chỉ được đưa một. let p: Pair<&str> = ... cho ra struct takes 2 generic arguments but 1 generic argument was supplied. Cách sửa: Pair<&str, i32>, hoặc Pair<_, _> để Rust tự điền cả hai. Option<i32, i32> thì ngược lại: enum takes 1 generic argument but 2 generic arguments were supplied. Nếu bạn muốn có hai giá trị, hãy dùng tuple, Option<(i32, i32)>.

Lifetime cũng được tính. Một struct Excerpt<'a> bị viết thành Excerpt<'a, 'a> cho ra struct takes 1 lifetime argument but 2 lifetime arguments were supplied. Cách sửa: mỗi tham số trong định nghĩa nhận một lifetime. Xem Lifetime. Một phương thức hoàn toàn không generic, như .into::<String>(), cho ra method takes 0 generic arguments but 1 generic argument was supplied; hãy viết let s: String = x.into(); thay vào đó (xem E0283).

Một điều bất ngờ: Vec<i32, i32> không phải E0107. Vec có tham số thứ hai, chưa ổn định, dành cho bộ cấp phát bộ nhớ (allocator), nên bạn nhận được E0658 (allocator_api) cùng vài lỗi khác. Cách sửa vẫn vậy: Vec<i32>, hoặc Vec<(i32, i32)> cho các cặp.

  • E0061: cùng sai sót đó với đối số thường, một hàm nhận N đối số nhưng được đưa M.
  • E0106: thiếu chỉ định lifetime, khi một tham chiếu trong kiểu cần 'a.
  • E0252: hai dòng use import cùng một tên; một E0107 thường theo sau khi Result mang nghĩa fmt::Result.
  • E0283: cần chú thích kiểu, cách sửa đúng khi .into::<T>() gây ra E0107.
  • E0401: một hàm bên trong dùng T generic của hàm bên ngoài.
  • 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. fn load() -> Result<String> gây ra E0107. Kiểu trả về nào sửa được lỗi cho một hàm đọc file?

2. Vì sao rustc nói at least 2 generic arguments với HashMap?

3. fn show(p: &Pair) báo `missing generics for struct Pair , và show được gọi với nhiều loại Pair` khác nhau. Cách sửa là gì?

Điểm: 0 / 3