为什么在扩展特质上提供便捷方法,而不是在特质本身上提供?

7
考虑标准库中的Iterator trait:
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) { ... }
    ...
}

考虑来自futures crate的StreamStreamExt traits:

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本质上是Iterator的异步版本。然而,我想引起你对它们之间的差异的注意。
为什么它们结构不同?
我唯一看到的拆分trait的好处是StreamExt方法不能被覆盖。这样它们就可以保证按照预期的行为,而Iterator的便利方法可能会被覆盖以显示不一致的行为。然而,我无法想象这是一个常见的问题,需要考虑对其进行保护。这种差异是以可访问性和可发现性为代价的,需要用户导入StreamExt才能使用它们,并首先知道它们的存在。
考虑到Stream是在Iterator之后出现的,显然这种拆分是一个有意识的决定,但是动机是什么呢?肯定不止我想到的那些。Iterator设计有什么不好吗? 当由另一个crate提供时,扩展trait肯定是必需的,但这个问题不是关于那个的。
1个回答

6
拆分的最大优点在于实现方便方法的特质可以在不同的板条箱中实现,而与核心方法不同。对于 Future vs FutureExt trait,这一点非常重要,因为它允许将 Future trait 的核心方法移动到 std 中,而不需要标准化 FutureExt 方便方法。
这有两个好处:首先,因为核心方法不依赖于分配器,所以 Future 可以进入 core;而某些方便方法可能需要分配器。其次,它减少了标准化表面积,以最小化标准化高优先级功能的成本,以标准化异步/等待功能。相反,现在可以继续在 futures crate 中迭代使用 FutureExt 中的便利方法。
那么为什么 Future vs FutureExt 与 Stream vs StreamExt 有关?首先,Stream 是 Future 的扩展,因此有一个遵循相同模式的论据。但更重要的是,人们预计在某个时候会标准化 Stream,并且可能会提供一些用于处理 async/await 的语法糖。通过现在拆分,将把核心功能迁移到 std/core 的成本最小化。长期计划似乎是将核心功能从 futures 移动到 std/core,而 futures 将成为更快速开发/风险更低的扩展功能位置。
作为历史注释,futures 的 0.1.x 版本使用了 Future 和 Stream 的迭代器风格的便利方法。这在 0.2.x 中已更改,作为实验/迭代的一部分,以准备 async/await。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接