使用 docopt 库查看以下代码:
const USAGE: &'static str = "...something...";
#[derive(Deserialize)]
struct Args {
flag: bool,
}
type Result<T> = result::Result<T, Box<error::Error + Send + Sync>>;
fn main() {
let mut args: Args = Docopt::new(USAGE)
.and_then(|d| d.deserialize())
.unwrap_or_else(|e| e.exit());
}
如果您查看等号右侧的表达式,您会发现它没有在任何地方提及Args
结构。编译器如何推断此表达式的返回类型?在 Rust 中,类型信息可以反向流动(从初始化目标到初始值设定项表达式)吗?
它是如何工作的?">对于Stack Overflow来说可能是一个太大的问题,但是(以及Scala和Haskell等其他语言)Rust的类型系统是基于Hindley-Milner类型系统的,尽管有许多修改和扩展。
简化一下,这个想法是将每个未知类型视为一个变量,并将类型之间的关系定义为一系列约束,然后可以通过算法解决。在某些方面,它类似于你可能在学校用代数解决的联立方程组。
类型推断是 Rust(以及扩展的 Hindley-Milner 家族中的其他语言)的一个特性,在惯用代码中普遍用于:
- 减少类型批注的干扰
- 通过在多个位置不对类型进行硬编码来提高可维护性 (DRY)
Rust 的类型推断功能强大,正如您所说,可以双向流动。若要将Vec<T>
用作更简单、更熟悉的示例,以下任何一项都是有效的:
let vec = Vec::new(1_i32);
let vec = Vec::<i32>::new();
let vec: Vec<i32> = Vec::new();
甚至可以仅根据以后使用类型的方式推断类型:
let mut vec = Vec::new();
// later...
vec.push(1_i32);
另一个很好的例子是根据预期的类型选择正确的字符串解析器:
let num: f32 = "100".parse().unwrap();
let num: i128 = "100".parse().unwrap();
let address: SocketAddr = "127.0.0.1:8080".parse().unwrap();
那么你原来的例子呢?
Docopt::new
返回一个Result<Docopt, Error>
,如果提供的选项不能解析为参数,则会Result::Err<Error>
。在这一点上,不知道参数是否有效,只是它们是正确的格式。- 接下来,
and_then
具有以下签名:
变量pub fn and_then<U, F>(self, op: F) -> Result<U, E> where F: FnOnce(T) -> Result<U, E>,
self
具有类型Result<T, E>
,其中T
Docopt
,E
Error
,从步骤 1 推导。U
仍然未知,即使您提供了关闭|d| d.deserialize()
. - 但我们知道
T
是Docopts
的,所以deserialize
是Docopts::deserialize
的,它的签名是:
变量fn deserialize<'a, 'de: 'a, D>(&'a self) -> Result<D, Error> where D: Deserialize<'de>
self
的类型为Docopts
。D
仍然未知,但我们知道它与步骤 2 中的U
类型相同。 Result::unwrap_or_else
具有以下签名:
变量fn unwrap_or_else<F>(self, op: F) -> T where F: FnOnce(E) -> T
self
的类型为Result<T, Error>
。但我们知道,T
与上一步的U
和D
相同。- 然后我们赋值给一个类型为
Args
的变量,因此上一步中的T
是Args
,这意味着步骤 3 中的D
(以及步骤 2 中的U
)也是Args
的。 - 编译器现在可以推断出,当你写
deserialize
时,你指的是方法<Args as Deserialize>::deserialize
,它是用#[derive(Deserialize)]
属性自动派生的。