#generic #content #traits #ownership #cow #level #framework

无 std static-cow

Cow 在类型级别:一个用于编写泛型内容所有权的类型的特性框架

3 个不稳定版本

0.2.0 2023 年 1 月 9 日
0.1.1 2023 年 1 月 3 日
0.1.0 2023 年 1 月 2 日

#2758Rust 模式


im-rope 中使用

Apache-2.0 WITH LLVM-exception

29KB
393

static-cow

此 Rust 包提供了一种特性框架,用于编写泛型内容所有权的类型,通过将 Cow 升级到类型级别,可以通过泛型类型参数指定特定对象是借用还是拥有。

Mascot

文档

请参阅 docs.rs 上的 API 文档

许可证

本项目根据 Apache License 2.0 以及 LLVM 异常 许可。除非你明确说明,否则你提交给 static-cow 的任何有意贡献,都应按照 Apache 2.0 以及 LLVM 异常许可,不附加任何额外条款或条件。


lib.rs:

此包提供了一种特性框架,用于编写泛型内容所有权的类型。

Mascot

API 概述

ToOwningIntoOwning

ToOwningIntoOwning 是本包提供的最通用特性,是你将在自己的类型上实现的特性。ToOwningstd::borrow::ToOwned 的一般化。

pub trait ToOwning {
    type Owning;
    fn to_owning(&self) -> Self::Owning;
}

ToOwned 不同,它不需要满足 Owning: Borrow<Self> 的条件。因此,ToOwning 代表了一种可以转换为自己拥有内容的版本,但不一定允许你从拥有者中获取原始借用类型的引用的类型。

ToOwningT where T : ToOwned + ?Sized 提供了一个泛型实现。这个泛型实现执行了显然的操作,让 Owning = Ownedto_owning = to_owned

IntoOwning 的声明很直观

pub trait IntoOwning ToOwning + Sized {
    fn into_owning(self) -> Self::Owning;
}

IntoOwningT where T : Clone 提供了一个泛型实现,使得 into_owning 成为一个恒等函数。因此,如果你的类型已经实现了 Clone,你将自动获得 IntoOwning 的实现。如果你手动实现 IntoOwning,则不能实现 Clone

实现了 ToOwningIntoOwning 的用户定义类型通常只需在其每个字段上调用 .to_owning().into_owning() 即可。最终会有衍生宏来实现这一点,但我还没有写。

StaticCow

StaticCow,这个包的名字,是将 std::borrow::Cow 提升到类型级别。虽然 Cow 是一个枚举,但 StaticCow 是一个特质。虽然 Cow::BorrowedCow::Owned 是枚举的变体,但这个包的 BorrowedOwned 是实现了 StaticCow 的元组结构(因此 Cow 也不例外)。因此,你不需要有字段 field: Cow<'a, B> 的结构体,你可以声明该字段为 field: S 并让 S 成为泛型参数 S: StaticCow<B>。然后,当在编译时已知 S 的所有权时,编译器可以生成适当特化的函数版本。

Cow 类似,StaticCow 需要满足 B : ToOwned,这使得它可以拥有 Deref<Target=B> 的超级特性。 IntoOwningStaticCow 的另一个超级特性。

幂等性

Idempotent 作为约束,可以让你对实现了 IntoOwning 但没有实现 ToOwned 的类型进行泛型编程。

StaticCow<B>Deref<Target=B> 作为超级特性,因此你可以对 StaticCow<B> 做任何你可以对 &B 做的事情。然而,为了提供这个超级特性,它的实现需要满足 B : ToOwned,这样它们就可以依赖 B::Owned : Borrow<B>

Idempotent 的要求较弱,因此它的功能也相应较弱,并且它不继承自 DerefToOwning 对 Owning 没有任何约束,这意味着从类型系统的角度来看,as far as the type system is concerned, .into_owning()is just a completely arbitrary conversion. 因此,你无法对可能是 T 或 T::Owning 但你不知道是哪一个的类型做任何有用的操作,因为它们不保证有任何特性是共同的。

Idempotent 提供了足够的信息,使其成为一个有用的约束

  1. 它可以提供 TT::Owning,并且会告诉你哪一个是哪个。

  2. 它对 T 进行了约束,使其满足 T::Owning::Owning = T::Owning。这意味着你可以多次调用 into_owning() 并始终得到一个 TT::Owning

Idempotent<T> 通过 Change<T> 实现,它包含一个 TKeep<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 创建 ToOwningIntoOwning 实例。

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 代替 SOwning 类型,以及将相同的操作应用于每个字段的 to_owninginto_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 中找到。

无运行时依赖