3 个不稳定版本
| 0.2.0 | 2023 年 1 月 9 日 |
|---|---|
| 0.1.1 | 2023 年 1 月 3 日 |
| 0.1.0 | 2023 年 1 月 2 日 |
#2758 在 Rust 模式
在 im-rope 中使用
29KB
393 行
static-cow
此 Rust 包提供了一种特性框架,用于编写泛型内容所有权的类型,通过将 Cow 升级到类型级别,可以通过泛型类型参数指定特定对象是借用还是拥有。
文档
请参阅 docs.rs 上的 API 文档。
许可证
本项目根据 Apache License 2.0 以及 LLVM 异常 许可。除非你明确说明,否则你提交给 static-cow 的任何有意贡献,都应按照 Apache 2.0 以及 LLVM 异常许可,不附加任何额外条款或条件。
lib.rs:
此包提供了一种特性框架,用于编写泛型内容所有权的类型。
API 概述
ToOwning 和 IntoOwning
ToOwning 和 IntoOwning 是本包提供的最通用特性,是你将在自己的类型上实现的特性。ToOwning 是 std::borrow::ToOwned 的一般化。
pub trait ToOwning {
type Owning;
fn to_owning(&self) -> Self::Owning;
}
与 ToOwned 不同,它不需要满足 Owning: Borrow<Self> 的条件。因此,ToOwning 代表了一种可以转换为自己拥有内容的版本,但不一定允许你从拥有者中获取原始借用类型的引用的类型。
ToOwning 对 T where T : ToOwned + ?Sized 提供了一个泛型实现。这个泛型实现执行了显然的操作,让 Owning = Owned 和 to_owning = to_owned。
IntoOwning 的声明很直观
pub trait IntoOwning ToOwning + Sized {
fn into_owning(self) -> Self::Owning;
}
IntoOwning 对 T where T : Clone 提供了一个泛型实现,使得 into_owning 成为一个恒等函数。因此,如果你的类型已经实现了 Clone,你将自动获得 IntoOwning 的实现。如果你手动实现 IntoOwning,则不能实现 Clone。
实现了 ToOwning 和 IntoOwning 的用户定义类型通常只需在其每个字段上调用 .to_owning() 和 .into_owning() 即可。最终会有衍生宏来实现这一点,但我还没有写。
StaticCow
StaticCow,这个包的名字,是将 std::borrow::Cow 提升到类型级别。虽然 Cow 是一个枚举,但 StaticCow 是一个特质。虽然 Cow::Borrowed 和 Cow::Owned 是枚举的变体,但这个包的 Borrowed 和 Owned 是实现了 StaticCow 的元组结构(因此 Cow 也不例外)。因此,你不需要有字段 field: Cow<'a, B> 的结构体,你可以声明该字段为 field: S 并让 S 成为泛型参数 S: StaticCow<B>。然后,当在编译时已知 S 的所有权时,编译器可以生成适当特化的函数版本。
与 Cow 类似,StaticCow 需要满足 B : ToOwned,这使得它可以拥有 Deref<Target=B> 的超级特性。 IntoOwning 是 StaticCow 的另一个超级特性。
幂等性
将 Idempotent 作为约束,可以让你对实现了 IntoOwning 但没有实现 ToOwned 的类型进行泛型编程。
StaticCow<B> 将 Deref<Target=B> 作为超级特性,因此你可以对 StaticCow<B> 做任何你可以对 &B 做的事情。然而,为了提供这个超级特性,它的实现需要满足 B : ToOwned,这样它们就可以依赖 B::Owned : Borrow<B>。
Idempotent 的要求较弱,因此它的功能也相应较弱,并且它不继承自 Deref。 ToOwning 对 Owning 没有任何约束,这意味着从类型系统的角度来看,as far as the type system is concerned, .into_owning()is just a completely arbitrary conversion. 因此,你无法对可能是 T 或 T::Owning 但你不知道是哪一个的类型做任何有用的操作,因为它们不保证有任何特性是共同的。
Idempotent 提供了足够的信息,使其成为一个有用的约束
-
它可以提供
T或T::Owning,并且会告诉你哪一个是哪个。 -
它对
T进行了约束,使其满足T::Owning::Owning = T::Owning。这意味着你可以多次调用into_owning()并始终得到一个T或T::Owning。
Idempotent<T> 通过 Change<T> 实现,它包含一个 T;Keep<T>,它包含一个 T::Owning;以及通过 ChangeOrKeep<T> 实现,它可能包含其中之一,运行时决定。在 Idempotent<T> 上调用 .to_owning() 或 .into_owning() 总是返回一个 Keep<T>。
示例
在这个示例中,我们将实现一个切片迭代器,它以相反的顺序返回切片的元素。最初,它会借用切片并在返回时克隆其元素。但它将实现 IntoOwning,这样在迭代过程中的任何时候,都可以将其转换为拥有 Vec 的迭代器。然后,它将从 Vec 的末尾弹出它返回的元素,而不进行克隆。
首先,我们声明我们的灵活迭代器
struct FlexIter<S, E> {
inner: S,
index: usize,
_phantom: PhantomData<[E]>,
}
E 是切片元素的类型。尽管约束没有出现在结构声明中,但 S 将是 StaticCow<[E]> 的实现。具体来说,S 将是 Borrowed<'b, [E]>,它封装了一个 &'b [E],或者它将是 Owned<[E]>,它封装了一个 Vec<E>。 index 比我们将返回的下一个元素的索引大一个,而 _phantom 是一个零大小对象,它必须存在以满足类型检查器,参数 E 必须出现在结构体字段中。
现在我们为 FlexIter 创建 ToOwning 和 IntoOwning 实例。
impl<S, E> ToOwning for FlexIter<S, E>
where
S: ToOwning,
{
type Owning = FlexIter<S::Owning, E>;
fn to_owning(&self) -> Self::Owning {
FlexIter {
inner: self.inner.to_owning(),
index: self.index.to_owning(),
_phantom: self._phantom.to_owning()
}
}
}
impl<S, E> IntoOwning for FlexIter<S, E>
where
S: IntoOwning,
{
fn into_owning(self) -> Self::Owning {
FlexIter {
inner: self.inner.into_owning(),
index: self.index.into_owning(),
_phantom: self._phantom.into_owning()
}
}
}
您可以看到,这些实现完全是照搬的:我们提供了一个与 Self 相同但用 S::Owning 代替 S 的 Owning 类型,以及将相同的操作应用于每个字段的 to_owning 和 into_owning 方法。
现在我们提供了一个借用迭代器的构造函数,该构造函数实现了 StaticCow<[E]>,使用 Borrowed<'b, [E]>。
impl<'b, E> FlexIter<'b, Borrowed<'b, [E]>, E> {
fn new(slice: &'b [E]) -> FlexIter<'b, Borrowed<'b, [E]>, E> {
FlexIter {
inner: Borrowed(slice),
index: slice.len(),
_phantom: CowPhantom::default(),
}
}
}
现在我们可以实现 Iterator
impl<S, E> Iterator for FlexIter<S, E>
where
E: Clone,
S: StaticCow<[E]>,
{
type Item = E;
fn next(&mut self) -> Option<Self::Item> {
// This is here to show that we can also access `inner` generically
// through its `Deref<Target=[E]>` implementation, without having to
// match on `mut_if_owned()`.
assert!(self.index <= self.inner.len());
match self.inner.mut_if_owned() {
// We're borrowing the slice, so we have to work inefficiently
// by cloning its elements before we return them.
MutIfOwned::Const(slice) => {
if self.index == 0 {
None
} else {
self.index -= 1;
Some(slice[self.index].clone())
}
}
// We own the slice as a `Vec`, so we can pop elements off of it
// without cloning.
MutIfOwned::Mut(vec) => {
// It's necessary to make sure we first truncate the vector
// to `index`, because we may have already started iterating
// before `.into_owned()` was called, and this may be our
// first time calling `.next()` since we took ownership. Of
// course we could have had our `into_owned` implementation
// do this instead of doing it here.
vec.truncate(self.index);
let ret = vec.pop()?;
self.index -= 1;
Some(ret)
}
}
}
}
现在让我们看看它的实际应用
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let mut borrowing_iter = FlexIter::new(numbers.borrow());
println!("Borrowing:");
println!("{}", borrowing_iter.next().unwrap());
println!("{}", borrowing_iter.next().unwrap());
let owning_iter = borrowing_iter.into_owning();
std::mem::drop(numbers);
println!("Owning:");
for item in owning_iter {
println!("{}", item);
}
}
运行此代码,我们得到预期的结果
Borrowing:
5
4
Owning:
3
2
1
此示例也可在 crate 源代码的 examples/flex_iter.rs 中找到。