在这个 Docopt 示例中,类型推断是如何工作的?



使用 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();

那么你原来的例子呢?

  1. Docopt::new返回一个Result<Docopt, Error>,如果提供的选项不能解析为参数,则会Result::Err<Error>。在这一点上,不知道参数是否有效,只是它们是正确的格式。
  2. 接下来,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>,其中TDocoptEError,从步骤 1 推导。U仍然未知,即使您提供了关闭|d| d.deserialize().
  3. 但我们知道TDocopts的,所以deserializeDocopts::deserialize的,它的签名是:
    fn deserialize<'a, 'de: 'a, D>(&'a self) -> Result<D, Error> 
    where
    D: Deserialize<'de>
    
    变量self的类型为DocoptsD仍然未知,但我们知道它与步骤 2 中的U类型相同。
  4. Result::unwrap_or_else具有以下签名:
    fn unwrap_or_else<F>(self, op: F) -> T 
    where
    F: FnOnce(E) -> T
    
    变量self的类型为Result<T, Error>。但我们知道,T与上一步的UD相同。
  5. 然后我们赋值给一个类型为Args的变量,因此上一步中的TArgs,这意味着步骤 3 中的D(以及步骤 2 中的U)也是Args的。
  6. 编译器现在可以推断出,当你写deserialize时,你指的是方法<Args as Deserialize>::deserialize,它是用#[derive(Deserialize)]属性自动派生的。

最新更新