是否可以返回一个 `Keys<'_, K, V>` 迭代器作为更通用的 `&K` 迭代器?

问题描述

我想编写一个返回 self.hash_map.keys()方法,同时对调用者隐藏具体类型 Keys<'_,K,V>

Keys 返回类型的一个缺点是它向调用者公开 K 元素来自 HashMap。直觉上,似乎没有必要公开这些信息。

  • 返回关键引用迭代器的最便宜的方法是什么(就 cpu/分配而言)?
  • 可以通过一些精确的返回类型选择来实现吗?是否可以进行某种形式的类型擦除?
  • 还是需要在函数体中调用?是否需要进行一些转换?

解决方法

您推测的两种选择都是可能的。

最简单的选择是使用 impl 类型语法,它是一种“存在”类型:“我将返回一个实现 Iterator 的值,但我不会告诉你具体类型是”。在这种情况下,编译器知道类型是什么(因此编译后的代码与没有隐藏的代码完全相同),但是你的方法的用户不能依赖于指定特征以外的任何东西,因此您不会泄露实现细节。

impl MyType {
    fn keys(&self) -> impl Iterator<Item = &MyKeyType> {
        self.hash_map.keys()
    }
}

(请注意,这与 dyn Iterator 类似但不相同;当您使用 dyn 时,您使用的是 runtime 调度,并且相同的函数可以返回不同的具体来自不同调用的类型。使用 impl,类型是静态的,只是隐藏的,并且没有开销。)

这个选项的缺点是类型完全无法命名;例如,没有人可以编写一个结构来保存您的 keys() 迭代器,除非使其成为所有 Iterator 的通用结构。 (这对于迭代器来说很少成为问题,因为无论如何迭代器包装器通常都是通用的。)

此外,如果您的迭代器实现了您希望调用者使用的任何其他特征,例如 DebugExactSizeIterator,那么您需要将它们添加到 impl 类型或它们将不可见。


另一种选择是将迭代器包装在您自己的结构中。这允许您隐藏实现类型,同时仍然允许调用者按名称引用它,因此它是最灵活的。此选项的缺点是您必须为包装器显式实现 Iterator(和任何其他特征):

impl MyType {
    fn keys(&self) -> MyKeyIterator<'_> {
        MyKeyIterator(self.hash_map.keys())
    }
}

#[derive(Clone,Debug)]
struct MyKeyIterator<'a>(Keys<'a,MyKeyType,MyValueType>);

impl<'a> Iterator for MyKeyIterator<'a> {
    type Item = &'a MyKeyType;

    fn next(&mut self) -> Option<&'a MyKeyType> {
        self.0.next()
    }
}

Rust Playground link with supporting code

这个包装器不应该增加任何性能成本(当使用优化编译时),除了默认情况下,如果从另一个 crate 调用包装器方法将不会被内联。如果您正在编写一个库并且此方法对性能敏感,则您可以在构建中启用链接时优化 (LTO),或者将 #[inline] 添加到 next 方法(它启用跨板条箱内联)。当然,不要在没有检查它是否对实际性能产生影响的情况下进行任何此类调整;否则,您只会增加编译时间(以及指令缓存抖动)。

,

可以通过一些精确的返回类型选择来实现吗?某种形式的类型擦除可能吗?

是的!您可以返回一个 impl Trait 以指示您正在返回一个实现 Trait 但不公开具体类型的类型:

fn keys(&self) -> impl Iterator<Item = &K> {
    self.hash_map.keys()
}

看到它在 playground 上工作。