为什么你不能使用? (问号)直接在 Rust 中的 collect() 上?

Why can you not use ? (question mark) directly on collect() in Rust?

假设我们有一个缓冲的 reader 并且想要从中收集行并使用 ?:

传播任何错误
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 具有通用类型

the documentation of Iterator::try that it takes a generic type called B, that that's the type that it returns. It's a bit unusual for a function to return a generic type (more commonly, they accept parameters which have generic types), but returning a generic type can make for some really interesting, versatile APIs, and collect is definitely one of them! The documentation of the collect method讲到这里可以看出一点。

稍微简化你的代码,这意味着编译器不能只看代码:

let lines = rdr.lines().collect();

并知道 lines 应该有什么类型。它可以推断出这一点(例如,如果你从函数中 returned lines 它可以告诉你 collect 的 return 类型应该与return 函数的类型,或者将其用作另一个函数的参数,它可以统一这些类型),但就其本身而言,这还不够上下文。

那么 - 编译器可以使用什么上下文来计算它应该收集到的类型......好吧,你有一个 ? 的事实给了它一个提示,但是:

? 不只是 Results

? 可用于实现(当前不稳定,可能即将更改)Try 特征的任何类型。这意味着编译器无法使用问号来推断您要收集到的类型是 Result - 也许您要收集到 Option 或其他实现的类型Try.

?也调用 into!

更糟糕的是,? 实际上不只是 return Result 的错误分支,它会在需要时尝试执行转换(例如,如果你有一个结果为 Result<(), Box<String>> 的函数,你可以写 Err(String::from("foo"))? 并且 Rust 会把它变成类似这样的东西:

if let Err(err) = Err(String::from("foo")) {
    return err.into();
}

因为为这些类型实现了 Into 特性,所以 String 可以用 Into 转换为 Box<String>,所以 ? 运算符可以省去您自己进行转换的麻烦。

然而,这增加了一个整体的间接级别,编译器不能总是做出一些“明显”的推断,因为错误类型 collect() returns 可能不是与函数的return类型相同,但可以转换为它!

类型归属

您可以放置​​此信息的一个地方是您可以指定变量的类型。所以你可以写这个代码:

let lines: Result<Vec<_>, _> = rdr.lines().collect();
let lines = lines?;

(每个 _s 都是一个地方,你要求编译器根据它可用的信息计算出应该存在的类型。事实上,let lines = rdr.lines().collect();let lines: _ = rdr.lines().collect(); 相同 - 如果编译器可以解决它,它会,但如果不能,它会给出一个错误,提示您需要提供更多信息)。

在此代码片段中,我们说“collect() 的 return 类型需要是 Result,其中 Ok 类型是 Vec 的东西(编译器,你解决了),Error 类型是,好吧,编译器也请解决!

这为编译器提供了相当多的信息,在很多情况下这就足够了,尽管有时我们可能需要自己填写更多 _

类型归属需要变量-输入turbofish

但是我上面写的代码比你写的代码要冗长一点。我们有两个语句而不是一个,我们创建两个变量而不是一个。而?运算符的很多意义就是让我们写出简洁的代码!

您找到的语法,俗称涡轮鱼(因为 ::<_> 看起来有点滑稽),是一种明确说明 collect 函数采用的泛型类型的方法:

collect::<Result<Vec<_>,_>>()

这就是说:调用函数collect,它的泛型类型应该是Result<Vec<_>, _>。就像我们指定变量的类型一样,这是一种向编译器提供函数的泛型类型信息的方法。

所以把所有这些放在一起,我们可以写成:

let lines = rdr.lines().collect::<Result<Vec<_>,_>>()?;

这就是对编译器说:我希望你调用 collect - 我希望收集到 return 的泛型类型是 VecResult一些东西(编译器可以解决其余的问题),然后它知道你要收集什么。