【问题标题】:Rust type mismatch but only if I don't use a type annotationRust 类型不匹配,但前提是我不使用类型注释
【发布时间】:2021-12-21 12:27:54
【问题描述】:

动机

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

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

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

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&lt;dyn Fn(&amp;Path) -&gt; Box&lt;dyn Iterator&lt;Item=i32&gt;&gt;&gt;,则编译:

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::new(|path: &amp;Path| -&gt; Box&lt;dyn Iterator&lt;Item = _&gt;&gt; { Box::new(... ) })
  • 我确定在某处我找不到重复项,但 Box&lt;T&gt;Box&lt;dyn Trait&gt; 在 Rust 中是完全不同的东西,并且具有不同的内存布局。如果您只是将Iterator 的某个实例装箱,它不会Box&lt;dyn Iterator&gt;。您必须将其转换为一个。

标签: rust typing


【解决方案1】:

指向动态大小类型 (DST) 的指针,如 Box&lt;dyn Iterator&lt;Item=i32&gt;&gt;"fat"Box&lt;std::vec::IntoIter&lt;i32&gt;&gt; 不是指向 DST 的指针(因为 IntoIter 的大小是已知的),因此可以是简单地指向堆上 IntoIter 的实例的“瘦”指针。

胖指针的创建和使用比瘦指针更昂贵。这就是为什么,正如@Aplet123 所提到的,您需要明确告诉编译器以某种方式(通过类型注释或as 转换)您要转换闭包生成的细Box&lt;std::vec::IntoIter&lt;i32&gt;&gt; 指针到胖 Box&lt;dyn Iterator&lt;Item=i32&gt;&gt; 指针。

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

【讨论】:

  • 这也很有帮助。虽然我仍然不明白为什么如果在参数列表中创建闭包,编译器能够正确地进行类型推断,但如果它是在上面创建的。
  • 除了整数文字,编译器必须知道let 绑定在声明时的类型。如果在没有注释的情况下声明了闭包,则编译器假定返回类型为Box&lt;IntoIter&gt;,原因在答案中——它不能使用未来的用法来确定let 绑定的类型,而是将来的用法必须与绑定的一致在声明时键入。但是,如果在参数列表中创建了闭包,编译器就知道该函数需要一个返回 Box&lt;dyn Iterator&gt; 的闭包,并且能够自动执行适当的强制转换。
  • 稍微澄清一下:编译器可以根据稍后出现的信息推断类型(例如let mut x = vec![]; x.push("hello"); 有效,但它不能为 闭包参数的类型 除了数字类型(浮点数也可以)
【解决方案2】:

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

但是,我建议这里可能不需要Box&lt;dyn Foo&gt;。确实,由于Iterator 是一个特征,所以你无法在编译时知道它的大小,从某种意义上说,你可以。

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

struct Foo<T> {
  value: T
}

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

它将生成Foo_str_'staticFoo_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);
}

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

【讨论】:

  • 这是有道理的。但是我现在有两种类型(FI),该函数是通用的。在实践中,foo 将成为结构的new 方法,如果我遵循这种方法,看起来我最终的结构必须是通用的FI。如果我想编写结构的类型,这会产生更多样板。有办法解决吗?
  • 一般来说,你可以依靠类型推断来弥补大部分的不足。在我给出的示例中,虽然foo 具有类型参数,但我们实际上不必指定它,编译器可以从上下文中推断出来。根据我的经验,这是“默认”,仅在需要显式类型参数的特殊情况下
  • 好的,所以我在我的实际代码中进行了这个更改,它确实有效。这样做我是否严格失去了一些灵活性,因为 F 类型必须在编译时知道?但作为回报,我可能会获得一些性能,假设 F 被调用了很多,不再有胖指针?
  • 注意使用泛型类型参数I作为返回类型意味着闭包必须总是返回与Iterator相同的type——见@ 987654321@ 在 Rust 书中了解更多信息。您是正确的,因为泛型使您失去了灵活性,但获得了运行时性能,因为 FI 在编译时必须是已知的。
  • 在运行时,FT 都必须提前知道。 EvilTak 的观点是正确的,您将无法在运行时选择 2 种不同类型的迭代器(除非您创建了某种包装 Box&lt;dyn Iterator&lt;Item = T&gt;&gt;WrappingIterator&lt;T&gt; 或类似的东西。不过,这一切都取决于您的需求。如果你发现你经常遇到这个问题,并且你不需要性能,也许转向盒式解决方案会更好。我的经验是“惯用的”锈代码往往依赖比Box&lt;dyn Foo&gt; 更重视泛型,但这只是我的经验
猜你喜欢
  • 2022-08-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多