为什么 Stream 在扩展特征而不是特征本身上提供便利方法?

Why does Stream provide convenience methods on an extension trait instead of the trait itself?

考虑标准库中的 Iterator 特性:

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) { ... }
    ...
}

并考虑 Stream and StreamExt traits from the futures 板条箱:

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 { ... }

它们有很多相似之处,因为 Stream 本质上是 Iteratorasync 版本。但是,我想提请注意它们的区别。

为什么它们的结构不同?

我看到的拆分特征的唯一好处是 StreamExt 方法不能被覆盖。通过这种方式,它们可以保证按预期运行,而 Iterator 的便捷方法可能会被覆盖 以导致行为不一致。但是,我无法想象这是一个需要考虑防范的常见问题。这种差异是以可访问性和可发现性为代价的,需要用户导入 StreamExt 才能使用它们并首先知道它们的存在。

考虑到 StreamIterator 之后出现,很明显分裂是一个深思熟虑的决定,但动机是什么?这肯定比我想象的要多。 Iterator 设计有什么 不好的地方 吗?

当另一个箱子提供时,扩展特征当然是必需的,但这个问题不是关于那个的。

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

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

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

作为历史记录,0.1.x 的 futures 使用迭代器样式的 Future 和 Stream 便利方法。这在 0.2.x 中被更改为导致 async/await.

的 experimentation/iteration 的一部分