WhydoesStreamprovideconveniencemethodsonanextensiontraitinsteadofthetraititself?

Consider the Iterator trait from the standard library:

pub trait Iterator {
    type Item;

    // required
    pub fn next(&mut self) -> Option<Self::Item>;

    // potentially advantageous to override
    pub fn size_hint(&self) -> (usize, Option<usize>) { ... }
    pub fn count(self) -> usize { ... }
    pub fn last(self) -> Option<Self::Item> { ... }
    pub fn advance_by(&mut self, n: usize) -> Result<(), usize> { ... }
    pub fn nth(&mut self, n: usize) -> Option<Self::Item> { ... }

    // convenience
    pub fn step_by(self, step: usize) -> StepBy<Self> { ... }
    pub fn chain<U>(self, other: U) -> Chain<Self, U::IntoIter> { ... }
    pub fn zip<U>(self, other: U) -> Zip<Self, U>::IntoIter> { ... }
    pub fn map<B, F>(self, f: F) -> Map<Self, F> { ... }
    pub fn for_each<F>(self, f: F) { ... }
    ...
}

And consider the Stream and StreamExt traits from the futures crate:

pub trait Stream {
    type Item;

    // required
    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>>;

    // potentially advantageous to override
    fn size_hint(&self) -> (usize, Option<usize>) { ... }
}

pub trait StreamExt: Stream {
    // convenience
    pub fn next(&mut self) -> Next<'_, Self> { ... }
    pub fn into_future(self) -> StreamFuture<Self> { ... }
    pub fn map<T, F>(self, f: F) -> Map<Self, F> { ... }
    pub fn enumerate(self) -> Enumerate<Self> { ... }
    pub fn filter<Fut, F>(self, f: F) -> Filter<Self, Fut, F> { ... }
    ...
}

impl<T> StreamExt for T where T: Stream { ... }

They have a lot of similarities, given that Stream is essentially an async version of Iterator. However, I'd like to draw attention to their differences.

Why are they structured differently?

The only benefit I see of splitting a trait is that the StreamExt methods can't be overridden. This way they are guaranteed to behave as intended, whereas the convenience methods for Iterator could be overridden to behave inconsistently. However, I can't imagine this being a common issue to consider guarding against it. And this difference comes at the cost of accessibility and discoverability, requiring users to import StreamExt to use them and to know they exist in the first place.

Given that Stream came after Iterator it is obvious the split was a deliberate decision, but what was the motivation? Surely its more than what I've thought up. Is there something bad about the Iterator design?

Extension traits are certainly required when provided by another crate but this question isn't about that.

回答

拆分的最大优点是实现便捷方法的 trait 可以在与核心方法不同的 crate 中实现。这对于 Future vs FutureExt trait 很重要,因为它允许 Future trait 的核心方法移动到 std 中,而无需标准化 FutureExt 便利方法。

这有两个优点:首先,Future 可以进入核心,因为核心方法不依赖于分配器,而一些方便的方法可能会。其次,它减少了标准化的表面积,以最小化标准化一个高优先级的特性的成本,以便标准化 async/await。相反,现在可以在 FutureExt 中继续在 futures crate 中迭代便利方法。

那么为什么 Future vs FutureExt 与 Stream vs StreamExt 相关?首先,Stream 是 Future 的扩展,因此有一个论据是遵循相同的模式。但更重要的是,人们期望在某个时候 Stream 将被标准化,可能会使用一些语法糖来处理 async/await。通过现在拆分,将核心功能迁移到 std/core 的成本被最小化。长期计划似乎是核心功能将从期货转移到标准/核心,而期货将成为扩展功能的更快开发/风险更低的位置。

作为历史记录,0.1.x 的期货使用了 Future 和 Stream 的便捷方法的迭代器样式。这在 0.2.x 中作为导致 async/await 的实验/迭代的一部分进行了更改。


以上是WhydoesStreamprovideconveniencemethodsonanextensiontraitinsteadofthetraititself?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>