Rust 类型不匹配但前提是我不使用类型注释

Rust type mismatch but only if I don't use a type annotation

动机

我想读取光盘上多个文件的值流。这些可能是 CSV 文件,或者制表符分隔的文件,或者一些专有的二进制格式。因此,我希望处理读取多个文件的函数将 Path -> Iterator<Data> 函数作为参数。如果我理解正确,在 Rust 中我需要将迭代器和函数本身装箱,因为它们没有大小。因此我的阅读功能应该是(我只是在这里使用 i32 作为我的数据的简单代理):

fn foo(read_from_file: Box<dyn Fn(&Path) -> Box<dyn Iterator<Item=i32>>>) {
    panic!("Not implemented");
}

为了测试,我不想从光盘中读取实际文件。我希望我的测试数据就在测试模块中。这大致是我想要的,但为了简单起见,我只是将它放入 bin 项目的主体中:

use std::path::Path;

fn foo(read_from_file: Box<dyn Fn(&Path) -> Box<dyn Iterator<Item=i32>>>) {
    panic!("Not implemented");
}

fn main() {

    let read_from_file = Box::new(|path: &Path| Box::new(match path.as_os_str().to_str().unwrap() {
        "/my_files/data.csv" => vec![1, 2, 3],
        "/my_files/data_2.csv" => vec![4, 5, 6],
        _ => panic!("Invalid filename"),
    }.into_iter()));

    foo(read_from_file);
}

错误

这给了我一个编译错误:

   Compiling iter v0.1.0 (/home/harry/coding/rust_sandbox/iter)
error[E0271]: type mismatch resolving `for<'r> <[closure@src/main.rs:9:35: 13:19] as FnOnce<(&'r Path,)>>::Output == Box<(dyn Iterator<Item = i32> + 'static)>`
  --> src/main.rs:15:9
   |
15 |     foo(read_from_file);
   |         ^^^^^^^^^^^^^^ expected trait object `dyn Iterator`, found struct `std::vec::IntoIter`
   |
   = note: expected struct `Box<(dyn Iterator<Item = i32> + 'static)>`
              found struct `Box<std::vec::IntoIter<{integer}>>`
   = note: required for the cast to the object type `dyn for<'r> Fn(&'r Path) -> Box<(dyn Iterator<Item = i32> + 'static)>`

For more information about this error, try `rustc --explain E0271`.
error: could not compile `iter` due to previous error

我不太明白。 std::vec::IntoIter 没有实现 Iterator,在这种情况下我不明白为什么这是一个输入错误?

修复,我也不明白

如果我添加显式类型注释 Box<dyn Fn(&Path) -> Box<dyn Iterator<Item=i32>>>,则编译:

use std::path::Path;

fn foo(read_from_file: Box<dyn Fn(&Path) -> Box<dyn Iterator<Item=i32>>>) {
    panic!("Not implemented");
}

fn main() {

    let read_from_file : Box<dyn Fn(&Path) -> Box<dyn Iterator<Item=i32>>>
        = Box::new(|path: &Path| Box::new(match path.as_os_str().to_str().unwrap() {
        "/my_files/data.csv" => vec![1, 2, 3],
        "/my_files/data_2.csv" => vec![4, 5, 6],
        _ => panic!("Invalid filename"),
    }.into_iter()));

    foo(read_from_file);

我很困惑为什么会这样。我对 Rust 的理解是,在 let 定义中,显式类型是可选的——除非编译器无法推断它,在这种情况下,编译器应该发出 error[E0283]: type annotations required.

指向 Box<dyn Iterator<Item=i32>> 等动态大小类型 (DST) 的指针是 Box<std::vec::IntoIter<i32>> 不是指向 DST 的指针(因为 IntoIter 的大小已知),因此可以是一个简单地指向堆上 IntoIter 实例的“瘦”指针.

胖指针的创建和使用比瘦指针更昂贵。这就是为什么,正如 @Aplet123 提到的,您需要以某种方式 显式告诉编译器 (通过类型注释或 as 强制转换)您想要强制转换瘦 Box<std::vec::IntoIter<i32>> 由你的闭包生成的指向胖 Box<dyn Iterator<Item=i32>> 指针的指针。

请注意,如果您删除 let 绑定并在 foo 函数调用的参数列表中创建闭包,则编译器会使闭包必须 return 一个胖指针因为 foo.

期望的参数类型

对我来说,这看起来像是类型推断的失败,因为闭包无法推断它需要 return 指向 v-table 的指针(来自 dyn Iterator).

不过,我建议 Box<dyn Foo> 在这里可能没有必要。确实,由于 Iterator 是一个特征,您无法在编译时知道它的大小,从某种意义上说,您可以。

Rust 将泛型代码“单态化”,这意味着它会为每个与之一起使用的具体类型生成泛型 functions/structs/etc 的副本。例如,如果您有:

struct Foo<T> {
  value: T
}

fn main() {
  let _ = Foo { value: "hello" };
  let _ = Foo { value: 123 };
}

它将生成一个 Foo_str_'static 和一个 Foo_i32(大致来说)并根据需要替换它们。

您可以利用此漏洞在使用特征时通过泛型使用静态分派。您的函数可以重写为:

fn foo<F, I>(read_from_file: F)
where
  F: Fn(&Path) -> I,
  I: Iterator<Item = i32>,
{
  unimplemented!()
}

fn main() {
  // note the lack of boxing
  let read_from_file = |path: &Path| {
    // ...
  };

  foo(read_from_file);
}

此代码(很可能但我还没有进行基准测试)更快、更惯用,并且使编译器错误消失。