Easier way to return `impl Iterator<Item = T>` with `DoubleEndedIterator`, `ExactSizeIterator` and `FusedIterator` features

huangapple go评论82阅读模式
英文:

Easier way to return `impl Iterator<Item = T>` with `DoubleEndedIterator`, `ExactSizeIterator` and `FusedIterator` features

问题

在Rust中,你可以在返回签名中使用impl Trait来简化返回类型。此外,它允许你静态地表示闭包的“未命名具体类型”。

在签名中只看到impl Iterator&lt;Item = T&gt;是相当常见的,这比确切的返回类型要容易得多。此外,我认为这还可以使你的API对返回的迭代器类型的任何更改保持稳定。例如,你可以将Vecstd::slice::Iter)替换为VecDequestd::collections::vec_deque::Iter)。

然而,我刚刚发现这有一个显著的缺点。在你需要访问DoubleEndedIteratorExactSizeIteratorFusedIterator提供的方法的情况下,你意外地减少了返回的迭代器的功能。

考虑这个示例在Rust Playground上...

#[derive(Clone, Debug, Default, Eq, PartialEq)]
struct NewVec&lt;T&gt;(Vec&lt;T&gt;);

impl&lt;T&gt; NewVec&lt;T&gt; {
    pub fn iter(&amp;self) -&gt; impl Iterator&lt;Item = &amp;T&gt; + DoubleEndedIterator {
        self.0.iter()
    }
}

fn main() {
    let palindrome = NewVec(Vec::from([&#39;R&#39;, &#39;A&#39;, &#39;C&#39;, &#39;E&#39;, &#39;C&#39;, &#39;A&#39;, &#39;R&#39;, &#39;!&#39;]));
    println!(&quot;{:?}&quot;, palindrome);

    println!(
        &quot;{}&quot;,
        palindrome
            .iter()
            .map(|character| format!(&quot;{}&quot;, character))
            .collect::&lt;Vec&lt;String&gt;&gt;()
            .join(&quot;&quot;)
    );

    println!(
        &quot;{}&quot;,
        palindrome
            .iter()
            .rev()
            .map(|character| format!(&quot;{}&quot;, character))
            .collect::&lt;Vec&lt;String&gt;&gt;()
            .join(&quot;&quot;)
    );
}

NewStruct上的iter方法不能只返回impl Iterator&lt;Item = &amp;T&gt;,而必须返回impl Iterator&lt;Item = &amp;T&gt; + DoubleEndedIterator以使rev方法工作。

这并不是世界末日,但现在签名需要impl Iterator&lt;Item = &amp;T&gt; + DoubleEndedIterator + ExactSizeIterator + FusedIterator,以避免降低返回的迭代器的功能。

是否有更好的方法来实现这一点?如果我不想限制底层迭代器,那么是否真的需要在每个返回迭代器的函数返回类型上添加impl Iterator&lt;Item = &amp;T&gt; + DoubleEndedIterator + ExactSizeIterator + FusedIterator

这是否也意味着,即使底层迭代器实际上实现了这些特性,如果返回的迭代器没有指定它也实现了ExactSizeIteratorFusedIterator,那么在某些情况下可能会有性能影响?

目前还没有办法创建trait别名。类型别名不适用于trait,而通过trait继承可以模拟trait别名,但这会引入一个新类型,现在需要导出该新类型。

英文:

In Rust you can use impl Trait in a return signature to simplify the return type. In addition, it allows you to statically represent the "unnamed concrete type" of closures.

It is pretty common to just see impl Iterator&lt;Item = T&gt; in the signature, which is much easier to write than the exact return type. In addition, I believe this also makes your API stable against any change to the type of the returned iterator. For instance, you could swap out a Vec (std::slice::Iter) for a VecDeque (std::collections::vec_deque::Iter).

However I just discovered that this has a significant drawback. In the case that you need access to methods provided by DoubleEndedIterator, ExactSizeIterator and FusedIterator you have now accidently reduced the features of your returned iterator.

Consider this example on the Rust playground...

#[derive(Clone, Debug, Default, Eq, PartialEq)]
struct NewVec&lt;T&gt;(Vec&lt;T&gt;);

impl&lt;T&gt; NewVec&lt;T&gt; {
    pub fn iter(&amp;self) -&gt; impl Iterator&lt;Item = &amp;T&gt; + DoubleEndedIterator {
        self.0.iter()
    }
}

fn main() {
    let palindrome = NewVec(Vec::from([&#39;R&#39;, &#39;A&#39;, &#39;C&#39;, &#39;E&#39;, &#39;C&#39;, &#39;A&#39;, &#39;R&#39;, &#39;!&#39;]));
    println!(&quot;{:?}&quot;, palindrome);

    println!(
        &quot;{}&quot;,
        palindrome
            .iter()
            .map(|character| format!(&quot;{}&quot;, character))
            .collect::&lt;Vec&lt;String&gt;&gt;()
            .join(&quot;&quot;)
    );

    println!(
        &quot;{}&quot;,
        palindrome
            .iter()
            .rev()
            .map(|character| format!(&quot;{}&quot;, character))
            .collect::&lt;Vec&lt;String&gt;&gt;()
            .join(&quot;&quot;)
    );
}

The iter method on NewStruct cannot just return impl Iterator&lt;Item = &amp;T&gt;, instead must return impl Iterator&lt;Item = &amp;T&gt; + DoubleEndedIterator in order for rev to work.

This isn't the end of the world, but now signatures are impl Iterator&lt;Item = &amp;T&gt; + DoubleEndedIterator + ExactSizeIterator + FusedIterator in order to avoid a reduction in the features of the returned iterator.

Is there a better way to achieve this? Do I really have to add impl Iterator&lt;Item = &amp;T&gt; + DoubleEndedIterator + ExactSizeIterator + FusedIterator on every function return type that returns an iterator if I do not want to restrict the underlying iterator?

Does this also imply that there may also be a performance impact in cases whereby the returned iterator does not specify that it also implements ExactSizeIterator and FusedIterator, even though underlying iterator actually does implement these traits?

There is no way to make trait aliases at the moment. Type aliases do not work for traits and a trait alias can be emulated with trait inheritance, however this introduces a new type which now needs to be exported.

答案1

得分: 2

你可以省略FusedIterator,因为根据文档的说明,它不应该在边界中使用,而应该作为fuse()中的特化使用,并且特化可以“看穿”impl Trait

你也可以省略Iterator,因为它是DoubleEndedIteratorExactSizeIterator的超级trait。

这样我们就得到了以下更简短的边界:

pub fn iter(&self) -> impl DoubleEndedIterator<Item = &T> + ExactSizeIterator {
    self.0.iter()
}
英文:

You can omit FusedIterator, since, as noted in the docs, it should not be used in bounds but rather used as specialization in fuse(), and specializations can "see through" impl Trait.

You can also omit Iterator, since it is a supertrait of both DoubleEndedIterator and ExactSizeIterator.

Which leaves us with these shorter bounds:

pub fn iter(&amp;self) -&gt; impl DoubleEndedIterator&lt;Item = &amp;T&gt; + ExactSizeIterator {
    self.0.iter()
}

huangapple
  • 本文由 发表于 2023年6月19日 10:01:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/76503213.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定