为什么 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
本质上是 Iterator
的 async
版本。但是,我想提请注意它们的区别。
为什么它们的结构不同?
我看到的拆分特征的唯一好处是 StreamExt
方法不能被覆盖。通过这种方式,它们可以保证按预期运行,而 Iterator
的便捷方法可能会被覆盖 以导致行为不一致。但是,我无法想象这是一个需要考虑防范的常见问题。这种差异是以可访问性和可发现性为代价的,需要用户导入 StreamExt
才能使用它们并首先知道它们的存在。
考虑到 Stream
在 Iterator
之后出现,很明显分裂是一个深思熟虑的决定,但动机是什么?这肯定比我想象的要多。 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 的一部分
考虑标准库中的 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
本质上是 Iterator
的 async
版本。但是,我想提请注意它们的区别。
为什么它们的结构不同?
我看到的拆分特征的唯一好处是 StreamExt
方法不能被覆盖。通过这种方式,它们可以保证按预期运行,而 Iterator
的便捷方法可能会被覆盖 以导致行为不一致。但是,我无法想象这是一个需要考虑防范的常见问题。这种差异是以可访问性和可发现性为代价的,需要用户导入 StreamExt
才能使用它们并首先知道它们的存在。
考虑到 Stream
在 Iterator
之后出现,很明显分裂是一个深思熟虑的决定,但动机是什么?这肯定比我想象的要多。 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 的一部分