【问题标题】:我可以根据闭包指定迭代器的类型吗?
【发布时间】:2022-01-23 16:13:25
【问题描述】:

我编写了一个函数来遍历二维网格中的相邻单元格:

pub fn neighbours(
    (width, height): (usize, usize),
    (x, y): (usize, usize),
) -> impl Iterator<Item = (usize, usize)> {
    [(-1, 0), (1, 0), (0, -1), (0, 1)]
        .iter()
        .map(move |(dx, dy)| (x as i64 + dx, y as i64 + dy))
        .filter(move |&(nx, ny)| 0 <= ny && ny < height as i64 && 0 <= nx && nx < width as i64)
        .map(|(nx, ny)| (nx as usize, ny as usize))
}

注意它返回一个impl Iterator&lt;Item = (usize, usize)&gt;

如果我理解正确,返回 impl 会导致代码变慢,调用函数指针而不是将事情编译成简单的循环。对吧?

所以想指定一个更精确的类型,我把类型替换为(),看看编译器推断出什么类型,它会推断出什么

std::iter::Map<std::iter::Filter<std::iter::Map<std::slice::Iter<'l, (i64, i64)>, _>, _>, _>

_s 代表我不知道如何指定其类型的闭包。

我尝试将闭包提取到具有 Fn 特征的结构,但无法使其正常工作,而且 IIUC implementing Fn traits is an unstable feature and I shouldn't use it"

【问题讨论】:

  • impl 只是语法糖iirc。它实际上将使用泛型并为您使用该函数的每种类型进行编译(这是您想要手动执行的操作)。但是让我们看看是否有人证实了这一点
  • 来自the book“impl Trait 语法让您可以简洁地指定函数返回实现 Iterator trait 的某种类型,而无需写出很长的类型。”。它不是动态的,例如你不能有一个分支并返回两种不同的类型。正如@Netwave 所说,当你因为太长或涉及闭包而无法编写类型时,它是一种工具
  • Iterator 不是最合适的返回类型吗?我认为如果要导出完整的内部类型,它会泄露很多关于实现的信息,并且每次实现更改时都需要更改。我对这些构造的编译表示不够熟悉,但我会假设在这种情况下更改类型注释也不会更改生成的代码。
  • 您可能将impl(如上所述,只是泛型)与dyn 混淆了。后者意味着返回一个"trait object",这将抹去具体类型的任何知识,并且确实会产生一些运行时成本,因为为了调用这些对象上的任何方法,必须首先查找vtable。
  • @Netwave 虽然impl 确实不是动态的,并且没有性能损失,但impl(在返回位置)只是不是语法糖。无法将返回 impl Trait 的函数在语法上转换为使用命名泛型的函数(这就是“语法糖”所暗示的)。引入 impl Trait 是为了支持期货的高效返回,这对于 async/await 至关重要,而当时的泛型无法实现这一点。

标签: rust types closures


【解决方案1】:

如果我理解正确,返回 impl 会导致代码变慢,调用函数指针而不是将事物编译成简单的循环。对吧?

不。返回 impl Iterator&lt;Item = (usize, usize)&gt; 在代码生成方面与返回 Map&lt;Filter&lt;Map&lt;...&gt;&gt;&gt; 完全相同。¹ 区别是双重的:

  • 更改neighbours 的返回类型不需要更改其签名,因此impl Iterator 与其返回类型的更改前向兼容。
  • impl Iterator 不会与其他不透明的 impl Iterator 类型统一,即使它们碰巧具有相同的底层类型。 (简单来说:编译器不允许您从不同的来源创建 Vecimpl Iterators,即使所有这些不透明类型都是相同的具体类型。)

这些差异都不会影响代码生成或编译器内联任何内容的能力,因此请继续使用impl Iterator

在一种情况下,您仍必须使用间接调度 (dyn Iterator):当函数 neighbours 本身是特征的一部分时,impl Trait 语法尚不可用(从 1.59 开始)。目前解决这个问题的最好方法是返回Box&lt;dyn Iterator&gt;。 (但是请注意,这并不意味着 每个 调用都会被动态调度;对 .next() 的调用将是,但“内部”的所有内容仍然使用易于优化的静态调度。)

相关问题


¹ 请注意,为了真正返回 Map&lt;Filter&lt;Map&lt;...&gt;&gt;&gt;,您仍然必须使用impl Trait 来表示闭包,因为它们具有匿名类型。

【讨论】:

    猜你喜欢
    • 2021-05-31
    • 2018-01-26
    • 1970-01-01
    • 2021-03-23
    • 1970-01-01
    • 2018-05-30
    • 2016-07-24
    • 1970-01-01
    相关资源
    最近更新 更多