Lỗi Rust E0631: type mismatch in closure arguments (sai kiểu tham số closure)
E0631 nghĩa là closure của bạn có đúng số lượng tham số, nhưng ít nhất một tham số sai kiểu. Chín trên mười lần, khác biệt chỉ là một dấu &: bạn viết |x: i32|, nhưng iterator lại cho bạn mượn &i32. Cách sửa nhanh nhất thường là xóa chú thích kiểu (type annotation) và để Rust tự suy ra. Trang này trình bày cách sửa đó trước, rồi giải thích dấu & từ đâu ra, để lần sau bạn đoán trước được. 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[E0631]: type mismatch in closure arguments --> src/main.rs:3:43 |3 | let doubled: Vec<i32> = prices.iter().map(|p: i32| p * 2).collect(); // ❌ items are &i32 | ^^^ -------- found signature defined here | | | expected due to this | = note: expected closure signature `fn(&{integer}) -> _` found closure signature `fn(i32) -> _`note: required by a bound in `map`help: consider adjusting the signature so it borrows its argument |3 | let doubled: Vec<i32> = prices.iter().map(|p: &i32| p * 2).collect(); // ❌ items are &i32 | +Trình biên dịch báo kiểu tham số của closure không khớp và gợi ý sửa chữ ký để closure mượn đối số (thêm &). Đây là chương trình gây ra lỗi:
fn main() { let prices = vec![120, 45, 300]; let doubled: Vec<i32> = prices.iter().map(|p: i32| p * 2).collect(); // ❌ items are &i32 println!("{:?}", doubled);}rustc còn in thêm một lỗi thứ hai, dài hơn, ngay sau đó: the method `collect` exists for struct `Map<...>`, but its trait bounds were not satisfied (E0599). Lỗi đó chỉ là tác dụng phụ. Sửa E0631 là nó biến mất.
Ý nghĩa
Phần tiêu đề “Ý nghĩa”Hai dòng note là phần cốt lõi của thông báo. Hãy đọc chúng như “phương thức muốn cái này, bạn lại viết cái kia”:
- expected
fn(&{integer}) -> _:mapsẽ gọi closure của bạn với một tham chiếu đến một số nguyên.{integer}nghĩa là “một kiểu số nguyên nào đó mà Rust chưa chốt”, còn_nghĩa là “kiểu trả về nào cũng được”. - found
fn(i32) -> _: closure của bạn nói rằng nó nhận mộti32theo giá trị.
Đặt chúng cạnh nhau thì khác biệt duy nhất là dấu &. Dòng help: cho thấy cách sửa chỉ một ký tự.
Hãy nghĩ đến quầy gửi áo khoác. Bạn đưa áo khoác và nhận lại một tấm vé. .iter() hoạt động y như vậy: nó không đưa cho bạn các phần tử, nó đưa vé dẫn đến chúng (tham chiếu). Một closure khăng khăng đòi chính cái áo sẽ không nhận tấm vé.
Vì sao Rust làm vậy
Phần tiêu đề “Vì sao Rust làm vậy”Mọi phương thức iterator quyết định chính xác kiểu mà nó truyền cho closure của bạn, và kiểu đó phụ thuộc vào cách bạn tạo iterator:
v.iter()cho mượn từng phần tử:&T.v.into_iter()(trên mộtVecbạn sở hữu) trao hẳn từng phần tử:T.filtervàfindlại cho bạn mượn phần tử thêm một lần nữa, nên trên.iter()bạn nhận&&T.sort_bycho bạn mượn hai phần tử:&Tvà&T.
Khi bạn bỏ trống kiểu tham số, Rust điền chúng từ phương thức, nên chúng luôn khớp. Khi bạn tự viết kiểu, Rust tin lời bạn và kiểm tra nó. Rust sẽ không âm thầm sao chép giá trị hay biến một tham chiếu thành giá trị sở hữu, vì như thế sẽ thay đổi hành vi của code (và với String, một bản sao tốn bộ nhớ và thời gian). Bài Iterator trình bày ba cách lấy một iterator, còn bài Mượn giải thích tham chiếu.
Cách sửa
Phần tiêu đề “Cách sửa”Cách sửa 1: Bỏ chú thích kiểu
Phần tiêu đề “Cách sửa 1: Bỏ chú thích kiểu”Hãy chọn cách này đầu tiên. Tham số closure hầu như không bao giờ cần ghi kiểu: Rust biết chính xác map, filter và sort_by truyền gì. Xóa : i32 là code biên dịch được:
fn main() { let prices = vec![120, 45, 300]; let ages = vec![12, 34, 17, 50]; let mut scores = vec![42, 7, 99, 15];
// không chú thích kiểu: Rust dùng đúng thứ iterator đưa ra let doubled: Vec<i32> = prices.iter().map(|p| p * 2).collect(); let adults: Vec<&i32> = ages.iter().filter(|age| **age >= 18).collect(); scores.sort_by(|a, b| b.cmp(a));
println!("{:?}", doubled); println!("{:?}", adults); println!("{:?}", scores);}Kết quả:
[240, 90, 600][34, 50][99, 42, 15, 7]p * 2 chạy được trên &i32 vì các toán tử số của Rust cũng nhận tham chiếu. Trong filter, age là một &&i32, nên **age đi qua cả hai tham chiếu để đến con số. Các phép so sánh như b.cmp(a) làm việc trực tiếp trên tham chiếu.
Cách sửa 2: Viết đúng kiểu mà phương thức thật sự truyền
Phần tiêu đề “Cách sửa 2: Viết đúng kiểu mà phương thức thật sự truyền”Chọn cách này khi bạn muốn giữ kiểu hiển thị rõ, ví dụ để một chuỗi lệnh dài dễ đọc hơn. Làm theo dòng help: của rustc và thêm &. Bạn cũng có thể đặt & trong mẫu (pattern): |&p| tách tham chiếu ra và cho bạn một bản sao bình thường của con số (cách này dùng được cho các kiểu Copy như số nguyên):
fn main() { let prices = vec![120, 45, 300]; let ages = vec![12, 34, 17, 50]; let mut scores = vec![42, 7, 99, 15];
// viết đúng kiểu thật: & cho .iter(), && bên trong filter let doubled: Vec<i32> = prices.iter().map(|p: &i32| p * 2).collect(); let adults: Vec<&i32> = ages.iter().filter(|age: &&i32| **age >= 18).collect(); scores.sort_by(|a: &i32, b: &i32| b.cmp(a));
// hoặc sao chép con số ra bằng mẫu & let tax: Vec<i32> = prices.iter().map(|&p| p / 10).collect();
println!("{:?}", doubled); println!("{:?}", adults); println!("{:?}", scores); println!("{:?}", tax);}Kết quả:
[240, 90, 600][34, 50][99, 42, 15, 7][12, 4, 30]Hãy để ý sự khác biệt: |p: &i32| nghĩa là “p là một tham chiếu”, còn |&p| nghĩa là “tôi nhận một tham chiếu, hãy đưa tôi thứ nằm sau nó”.
Cách sửa 3: Đổi thứ mà iterator đưa ra
Phần tiêu đề “Cách sửa 3: Đổi thứ mà iterator đưa ra”Chọn cách này khi bạn thật sự muốn giá trị, không phải tham chiếu. Có hai cách để có chúng:
.iter().copied()biến mỗi&i32thành mộti32. Dùng nó cho các kiểuCopynhỏ như số,charvàbool. (.cloned()làm điều tương tự cho các kiểu nhưString, bằng cách clone.).into_iter()trên mộtVecđưa ra chính các phần tử. Sau đóVecbị dùng hết, nên hãy chọn cách này khi bạn không còn cần nó nữa.
fn main() { let prices = vec![120, 45, 300];
// .copied() biến các phần tử &i32 thành phần tử i32 let doubled: Vec<i32> = prices.iter().copied().map(|p: i32| p * 2).collect(); println!("{:?}", doubled);
// .into_iter() trên một Vec đưa ra chính các phần tử (và dùng hết Vec) let names = vec![String::from("ferris"), String::from("corro")]; let loud: Vec<String> = names.into_iter().map(|name: String| name.to_uppercase()).collect(); println!("{:?}", loud);}Kết quả:
[240, 90, 600]["FERRIS", "CORRO"]Nếu bạn vẫn cần names sau vòng lặp, hãy giữ .iter() và nhận &String. Việc move ra khỏi một Vec mà bạn chỉ mượn là một lỗi khác, E0507.
Cách sửa 4: Cho hàm có tên nhận đúng thứ iterator đưa ra
Phần tiêu đề “Cách sửa 4: Cho hàm có tên nhận đúng thứ iterator đưa ra”Chọn cách này khi bạn truyền một hàm theo tên, như .map(shout). Khi đó thông báo ghi “function” thay vì “closure”:
error[E0631]: type mismatch in function arguments --> src/main.rs:7:51 |1 | fn shout(word: &String) -> String { | --------------------------------- found signature defined here...7 | let loud: Vec<String> = words.into_iter().map(shout).collect(); | --- ^^^^^ expected due to this | | | required by a bound introduced by this call | = note: expected function signature `fn(&str) -> _` found function signature `fn(&String) -> _`Các phần tử là &str, nhưng shout lại muốn &String. Một thói quen tốt: cho các hàm chỉ đọc văn bản nhận &str. Khi đó chúng nhận được chuỗi literal, &String và nhiều thứ khác. Khi iterator thêm một dấu & nữa (như filter), hãy bọc lời gọi trong một closure ngắn. Bên trong một lời gọi hàm bình thường, Rust tự bỏ bớt các dấu & thừa giúp bạn (gọi là deref coercion), nhưng nó không làm được điều đó khi bạn truyền hàm theo tên:
// nhận &str: dùng được cho chuỗi literal, &String và nhiều thứ khácfn shout(word: &str) -> String { word.to_uppercase()}
fn is_long(word: &str) -> bool { word.len() > 4}
fn main() { let words = vec!["crab", "rustacean", "ferris"];
// các phần tử là &str, nên có thể truyền shout theo tên let loud: Vec<String> = words.iter().copied().map(shout).collect(); println!("{:?}", loud);
// filter đưa ra &&str: một closure nhỏ để Rust tự chuyển đổi giúp bạn let long: Vec<&str> = words.iter().copied().filter(|w| is_long(w)).collect(); println!("{:?}", long);}Kết quả:
["CRAB", "RUSTACEAN", "FERRIS"]["rustacean", "ferris"]Các từ ở ví dụ này được giữ tiếng Anh vì kết quả phụ thuộc vào độ dài byte (len()) và việc đổi chữ hoa. Xem thêm về &str và String trong Chuỗi.
Thường gặp ở đâu
Phần tiêu đề “Thường gặp ở đâu”Sắp xếp với tham số có ghi kiểu. scores.sort_by(|a: i32, b: i32| b.cmp(&a)) cho ra E0631 với expected closure signature `for<'a, 'b> fn(&'a {integer}, &'b {integer}) -> _`. Phần for<'a, 'b> chỉ có nghĩa là “với mọi lần mượn”. Cách sửa: |a, b| b.cmp(a). Xem Sắp xếp.
Các mục của map với kiểu sở hữu. stock.iter().map(|(name, count): (String, i32)| ...) trên một HashMap<String, i32> cho ra expected closure signature `fn((&String, &_)) -> _`. .iter() trên một map cho mượn cả key lẫn value. Cách sửa: bỏ chú thích kiểu, hoặc viết (&String, &i32).
Hàm generic của chính bạn. Nếu fn apply<F: Fn(i32) -> i32>(f: F, x: i32) nhận một closure |x: f64| x / 2.0, rustc báo expected closure signature `fn(i32) -> _` và found closure signature `fn(f64) -> _`, chỉ vào ràng buộc. Cách sửa: cho closure nhận i32 (và chuyển đổi bên trong, x as f64 / 2.0), hoặc đổi ràng buộc nếu bạn thật sự muốn f64. Xem Trait của closure.
for_each với String. words.iter().for_each(|w: String| ...) trên một Vec<&str> mong đợi fn(&&str). Một &str không phải là String. Cách sửa: |w|, hoặc |w: &&str|.
Lỗi liên quan
Phần tiêu đề “Lỗi liên quan”- E0593: closure takes the wrong number of arguments: lỗi “chị em”, về số lượng tham số (ví dụ
|a, b|trong khi chỉ một tuple được truyền vào). - E0057: closure called with the wrong number of arguments: bạn tự gọi một closure và truyền quá ít hoặc quá nhiều giá trị.
- E0308: mismatched types: lỗi “sai kiểu” chung, ví dụ khi giá trị trả về của closure sai kiểu, hoặc với tham số con trỏ
fn(...). - E0599: no method found: lỗi “trait bounds were not satisfied” của
collecthoặcsumtheo sau E0631. Nó biến mất cùng E0631. - E0507: cannot move out of a borrowed value: khi bạn cố lấy quyền sở hữu các phần tử mà bạn chỉ mượn bằng
.iter(). - 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. let v = vec![1, 2, 3]; Closure trong v.iter().map(|x| ...) nhận kiểu gì?
2. Ghi chú nói expected closure signature fn(&{integer}) -> _, found closure signature fn(i32) -> _. Cách sửa nhanh nhất là gì?
3. |&p| p * 2 làm gì bên trong prices.iter().map(...)?
Điểm: 0 / 3
Đọc thêm
Phần tiêu đề “Đọc thêm”- E0631 — Rust error codes index (tiếng Anh): giải thích chính thức về lỗi này.
- Processing a Series of Items with Iterators — The Rust Book (tiếng Anh):
iter,into_itervàiter_mut, và mỗi cái đưa ra gì. - Closures — The Rust Book (tiếng Anh): kiểu tham số closure được suy luận ra sao, và khi nào nên tự viết.
Iterator::copied— tài liệu thư viện chuẩn (tiếng Anh): biến một iterator của&Tthành iterator củaT.slice::sort_by— tài liệu thư viện chuẩn (tiếng Anh): chữ ký cho thấy closure so sánh nhận hai tham chiếu.