为什么不能用?(问号)直接在Rust中的collect()上?
假设我们有一个缓冲阅读器,并希望从中收集行并使用?以下方法传播任何错误:
let lines: Vec<_> = rdr.lines().collect()?;
这不会编译,因为 error[E0277]: the size for values of type `str` cannot be known at compilation time
搜索答案是根据https://github.com/rust-lang/rust/issues/49391
let lines: Vec<_> = rdr.lines().collect::<Result<Vec<_>,_>>()?;
我的问题是需要这种::<Result<Vec<_>,_>>方式,以及这究竟意味着什么以及正在做什么。
请记住,我刚刚开始学习 Rust。
回答
有几个部分组合在一起意味着编译器需要一点帮助:
collect 有一个泛型类型
我们可以从文档中Iterator::try看到它接受一个名为 的泛型类型B,这就是它返回的类型。函数返回泛型类型有点不寻常(更常见的是,它们接受具有泛型类型的参数),但返回泛型类型可以产生一些非常有趣的通用 API,并且collect绝对是其中之一!该collect方法的文档对此进行了一些讨论。
稍微简化一下代码,这意味着编译器不能只看代码:
let lines = rdr.lines().collect();
并知道lines应该有什么类型。它可以推断出这一点(例如,如果您lines从函数返回,它可以判断的返回类型collect应该与函数的返回类型完全相同,或者将其用作另一个它可以统一的参数这些类型),但就其本身而言,这还不够。
那么 - 编译器可以使用什么上下文来计算它应该收集到的类型......好吧,你有 a 的事实?给了它一个提示,但是:
?不只是为了Results
?可用于实现(当前不稳定,可能即将改变)Try特性的任何类型。这意味着编译器不能使用问号来推断您收集到的类型是 a Result- 也许您正在收集到 anOption或其他实现Try.
? 还叫into!
更糟糕的是,?实际上并不只是返回 a 的错误分支Result,它会在需要时尝试执行转换(例如,如果您有一个Result<(), Box<String>>可以编写结果的函数,Err(String::from("foo"))?并且 Rust 会将其转换为类似:
if let Err(err) = Err(String::from("foo")) {
return err.into();
}
因为Intotrait 是为这些类型实现的,所以 aString可以转换为Box<String>with Into,所以?操作符可以省去您自己进行转换的需要。
但是,这增加了整个间接级别,编译器不能总是做出一些“明显”的推断,因为collect()返回的错误类型可能与函数的返回类型不同,而可以转换为它!
类型归属
您可以放置此信息的一个地方是您可以归因于变量的类型。所以你可以写这样的代码:
let lines: Result<Vec<_>, _> = rdr.lines().collect();
let lines = lines?;
(这些_s 中的每一个都是您要求编译器根据它可用的信息计算出应该存在的类型的地方。实际上,let lines = rdr.lines().collect();与let lines: _ = rdr.lines().collect();- 如果编译器可以计算出相同,它会,但如果它不能,它会给出一个错误,说你需要给它更多的信息)。
在此代码片段中,我们说“返回类型 ofcollect()需要是 a类型,Result其中Ok类型是某个Vec东西(编译器,您自己解决),而Error类型是,好吧,编译器也请解决这个问题!
这为编译器提供了相当多的信息,在很多情况下就足够了,尽管有时我们需要自己填写更多的信息_。
类型归属需要一个变量 - 输入 turbofish
但是我上面写的代码比你写的代码要冗长一些。我们有两个陈述而不是一个,我们制作了两个变量而不是一个。而且很多?操作符的意义在于让我们写出简洁的代码!
您发现的语法,俗称 turbofish(因为::<_>看起来有点有趣),是一种明确说明collect函数采用的泛型类型的方法:
collect::<Result<Vec<_>,_>>()
这就是说:调用函数collect,它的泛型类型应该是Result<Vec<_>, _>。就像我们指定变量的类型一样,这是一种向编译器提供关于函数的泛型类型应该是什么的信息的方法。
所以把所有这些放在一起,我们可以写:
let lines = rdr.lines().collect::<Result<Vec<_>,_>>()?;
这就是对编译器说的:我希望你调用collect- 我希望 collect 返回的泛型类型是Resulta Vecof something(编译器可以计算出其余的),然后它知道你要收集什么.