Rust 错误 E0631:type mismatch in closure arguments(闭包参数类型不匹配)
E0631 的意思是:你的闭包参数个数对了,但至少有一个参数的类型不对。十有八九,差别只在一个 &:你写的是 |x: i32|,而迭代器借给你的是 &i32。最快的修法往往是删掉类型注解,让 Rust 自己推断。本页先给出这个修法,再解释 & 是从哪儿来的,下次你就能提前料到。这个系列的其他错误,请看 Rust 编译错误详解。
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 | +第一行的意思是“闭包参数类型不匹配”。help 建议你“调整一下签名,让它借用参数”。
产生这个错误的程序是这样的:
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 紧接着还会打印第二个更长的错误:the method `collect` exists for struct `Map<...>`, but its trait bounds were not satisfied(E0599,方法 collect 存在,但不满足它的 trait 约束)。那只是连带出来的问题,修好 E0631 它就会消失。
这个错误是什么意思
Section titled “这个错误是什么意思”两行 note 是这条信息的核心。把它们读成“方法想要的是这个,你写的却是那个”:
- expected
fn(&{integer}) -> _:map调用你的闭包时,会传入一个整数的引用。{integer}表示“某种 Rust 还没确定下来的整数类型”,_表示“任意返回类型”。 - found
fn(i32) -> _:你的闭包声明它按值接收一个i32。
把两行并排一看,唯一的区别就是那个 &。help: 那一行给出了只改一个字符的修法。
可以想想衣帽间:你交出外套,拿回一张号牌。.iter() 也是这样:它交给你的不是元素本身,而是指向元素的号牌(引用)。一个非要外套本身的闭包,是不会收号牌的。
Rust 为什么要这样做
Section titled “Rust 为什么要这样做”每个迭代器方法都明确决定了它传给闭包的类型,而这个类型取决于你是怎样得到迭代器的:
v.iter()借出每个元素:&T。v.into_iter()(在你拥有的Vec上)把每个元素交出去:T。filter和find会把元素再借一次,所以在.iter()之上你得到的是&&T。sort_by借给你两个元素:&T和&T。
不写参数类型时,Rust 会根据方法把它们补上,所以总是对得上。你自己写了类型,Rust 就会当真,并检查它。它不会悄悄复制值,也不会把引用变成拥有所有权的值,因为那会改变代码的行为(而且对 String 来说,复制要花内存和时间)。迭代器那一课讲了获取迭代器的三种方式,借用讲了引用。
修复 1:去掉类型注解
Section titled “修复 1:去掉类型注解”先试这个。闭包参数几乎从来不需要写类型:Rust 清楚地知道 map、filter 和 sort_by 会传什么。删掉 : i32,代码就能编译了:
fn main() { let prices = vec![120, 45, 300]; let ages = vec![12, 34, 17, 50]; let mut scores = vec![42, 7, 99, 15];
// 不写类型注解:Rust 会用迭代器真正给出的类型 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);}输出:
[240, 90, 600][34, 50][99, 42, 15, 7]p * 2 能用在 &i32 上,因为 Rust 的数字运算符也接受引用。在 filter 里,age 是 &&i32,所以要用 **age 穿过两层引用拿到数字。b.cmp(a) 这样的比较可以直接用在引用上。
修复 2:写上方法真正传入的类型
Section titled “修复 2:写上方法真正传入的类型”当你想让类型一目了然时选这个,比如让一条很长的调用链更好读。照着 rustc 的 help: 加上 & 就行。也可以把 & 写在模式里:|&p| 会拆开这个引用,给你一份数字的普通副本(这适用于整数这类 Copy 类型):
fn main() { let prices = vec![120, 45, 300]; let ages = vec![12, 34, 17, 50]; let mut scores = vec![42, 7, 99, 15];
// 写出真正的类型:.iter() 是 &,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));
// 或者用 & 模式把数字复制出来 let tax: Vec<i32> = prices.iter().map(|&p| p / 10).collect();
println!("{:?}", doubled); println!("{:?}", adults); println!("{:?}", scores); println!("{:?}", tax);}输出:
[240, 90, 600][34, 50][99, 42, 15, 7][12, 4, 30]注意两者的区别:|p: &i32| 的意思是“p 就是一个引用”,而 |&p| 的意思是“我收到一个引用,把它指向的东西给我”。
修复 3:改变迭代器交出来的东西
Section titled “修复 3:改变迭代器交出来的东西”当你确实想要值本身、而不是引用时,选这个。有两种办法:
.iter().copied()把每个&i32变成i32。适用于数字、char、bool这类小的Copy类型。(.cloned()通过克隆,对String这类类型做同样的事。)- 在
Vec上调用.into_iter(),交出来的是元素本身。之后这个Vec就被用掉了,所以在你不再需要它时才选这个。
fn main() { let prices = vec![120, 45, 300];
// .copied() 把 &i32 元素变成 i32 元素 let doubled: Vec<i32> = prices.iter().copied().map(|p: i32| p * 2).collect(); println!("{:?}", doubled);
// 在 Vec 上调用 .into_iter(),交出的是元素本身(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);}输出:
[240, 90, 600]["FERRIS", "CORRO"]如果循环之后你还需要 names,就继续用 .iter(),改为接收 &String。从一个只是借来的 Vec 里把元素移出来,是另一个错误:E0507。
修复 4:让有名字的函数接收迭代器给出的类型
Section titled “修复 4:让有名字的函数接收迭代器给出的类型”当你按名字传函数时选这个,比如 .map(shout)。这时信息里说的是“function”(函数),而不是“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) -> _`元素是 &str,shout 要的却是 &String。一个好习惯:只读取文本的函数就接收 &str,这样字符串字面量、&String 等等它都能接受。当迭代器多加了一层 &(比如 filter)时,就用一个小闭包把调用包起来。在普通的函数调用里,Rust 会替你去掉多余的 &(这叫解引用强制转换,deref coercion),但按名字传函数时它做不到这一点:
// 接收 &str:字符串字面量、&String 等等都能用fn shout(word: &str) -> String { word.to_uppercase()}
fn is_long(word: &str) -> bool { word.len() > 4}
fn main() { let words = vec!["crab", "rustacean", "ferris"];
// 元素是 &str,所以 shout 可以按名字传入 let loud: Vec<String> = words.iter().copied().map(shout).collect(); println!("{:?}", loud);
// filter 给的是 &&str:用一个小闭包,让 Rust 替你转换 let long: Vec<&str> = words.iter().copied().filter(|w| is_long(w)).collect(); println!("{:?}", long);}输出:
["CRAB", "RUSTACEAN", "FERRIS"]["rustacean", "ferris"]这里的单词保留英文,因为 is_long 比较的是 len(),也就是字节数,换成中文结果就变了。&str 和 String 的区别,详见字符串。
排序时写了参数类型。 scores.sort_by(|a: i32, b: i32| b.cmp(&a)) 会得到 E0631,并提示 expected closure signature `for<'a, 'b> fn(&'a {integer}, &'b {integer}) -> _`。for<'a, 'b> 这部分只是说“对任意借用都成立”。修复:|a, b| b.cmp(a)。参见排序。
给 map 的条目写了拥有所有权的类型。 在 HashMap<String, i32> 上写 stock.iter().map(|(name, count): (String, i32)| ...),会得到 expected closure signature `fn((&String, &_)) -> _`。map 上的 .iter() 会把键和值都借出来。修复:去掉注解,或者写成 (&String, &i32)。
你自己写的泛型函数。 如果 fn apply<F: Fn(i32) -> i32>(f: F, x: i32) 收到的是闭包 |x: f64| x / 2.0,rustc 会报告 expected closure signature `fn(i32) -> _` 和 found closure signature `fn(f64) -> _`,并指向那个约束。修复:让闭包接收 i32(在里面再转换,x as f64 / 2.0);如果你本来就想要 f64,就改约束。参见闭包 trait。
for_each 里写了 String。 在 Vec<&str> 上写 words.iter().for_each(|w: String| ...),期望的是 fn(&&str)。&str 不是 String。修复:写 |w|,或者 |w: &&str|。
- E0593:闭包参数个数不对:姊妹错误,针对的是参数的个数(比如传入一个元组,你却写了
|a, b|)。 - E0057:调用闭包时参数个数不对:你自己调用闭包时,传的值太少或太多。
- E0308:类型不匹配:一般性的“类型不对”错误,比如闭包的返回值类型不对,或者参数是
fn(...)指针。 - E0599:找不到方法:紧跟在 E0631 后面、关于
collect或sum的“trait bounds were not satisfied”错误。修好 E0631,它也就消失了。 - E0507:不能从借用的值中移出:你想拿走
.iter()只是借给你的元素的所有权。 - 所有编译错误:错误页面的完整列表。
小测验
1. let v = vec![1, 2, 3]; 那么 v.iter().map(|x| ...) 里的闭包收到的是什么类型?
2. note 写着 expected closure signature fn(&{integer}) -> _ 和 found closure signature fn(i32) -> _。最快的修法是什么?
3. 在 prices.iter().map(...) 里,|&p| p * 2 做了什么?
得分: 0 / 3
- E0631 — Rust error codes index(英文):这个错误的官方解释。
- 使用迭代器处理元素序列 — Rust 程序设计语言:
iter、into_iter和iter_mut,以及它们各自交出来的是什么。 - 闭包:可以捕获其环境的匿名函数 — Rust 程序设计语言:闭包参数类型怎样推断,以及什么时候该自己写出来。
Iterator::copied— 标准库文档(英文):把&T的迭代器变成T的迭代器。slice::sort_by— 标准库文档(英文):从签名能看出,比较用的闭包收到的是两个引用。