为什么你不能使用? (问号)直接在 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 函数的类型,或者将其用作另一个函数的参数,它可以统一这些类型),但就其本身而言,这还不够上下文。
那么 - 编译器可以使用什么上下文来计算它应该收集到的类型......好吧,你有一个 ?
的事实给了它一个提示,但是:
?
不只是 Result
s
?
可用于实现(当前不稳定,可能即将更改)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 的泛型类型是 Vec
的 Result
一些东西(编译器可以解决其余的问题),然后它知道你要收集什么。
假设我们有一个缓冲的 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 函数的类型,或者将其用作另一个函数的参数,它可以统一这些类型),但就其本身而言,这还不够上下文。
那么 - 编译器可以使用什么上下文来计算它应该收集到的类型......好吧,你有一个 ?
的事实给了它一个提示,但是:
?
不只是 Result
s
?
可用于实现(当前不稳定,可能即将更改)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 的泛型类型是 Vec
的 Result
一些东西(编译器可以解决其余的问题),然后它知道你要收集什么。