2 个不稳定版本
0.2.0 | 2024年5月29日 |
---|---|
0.1.0 | 2024年4月1日 |
#646 在 Rust 模式
每月 98 次下载
32KB
598 行
Liftor
将给定的生命周期映射到具体类型。
"Liftor" 这个名字来源于 生命周期 和 函子。就像函子是类型到类型的映射一样,Liftor 是生命周期到类型的映射。它也可以被看作是“提升”一个具体类型到一个由任何生命周期参数化的类型集合中。
此crate为常见的用例提供了几个 Liftor
实现
Owned<T>
将任何生命周期'a
映射到T
(忽略生命周期)。Ref<T>
将任何生命周期'a
映射到&'a T
。RefMut<T>
将任何生命周期'a
映射到&'a mut T
。
您也可以为您的类型实现 Liftor
。例如,您可能有一个借用了字符串的结构体。您可以为此结构体实现 Liftor
以允许重新参数化它所借用的字符串的生命周期。
use liftor::Liftor;
struct Contact<'a> {
name: &'a str,
phone: &'a str,
}
impl<'a> Liftor<'a> for Contact<'a> {
// Remap the lifetime.
type In<'b> = Contact<'b> where 'a: 'b;
}
此crate的预期用途是将泛型关联类型与生命周期参数从关联类型的某些其他限制或不便属性中解耦。它在需要泛化引用和非引用类型的更高阶函数或特质中最有用,或在传递给回调的数据的生命周期在定义时不清楚的情况下。
动机
泛型关联类型是一个强大的功能,它允许一个特质表达生命周期到类型的映射。然而,存在一些微妙的限制,使得表达某些事物变得困难或不可能。例如
- 一个特质只能有一个给定类型的实现。
- 函数参数的边界
考虑一个使用泛型关联类型(GAT)来实现“工厂”模式的特性,其中工厂的输出可以借用工厂本身拥有的数据。例如
trait Factory {
type Item<'a> where Self: 'a;
fn create(&self) -> Self::Item<'_>;
}
这个特性似乎可以干净地抽象“工厂”模式。由于泛型关联类型映射将生命周期映射到具体类型,不同的Factory
实现可以表达它们是否从工厂借用数据,甚至在需要的情况下,工厂实现可以返回共享实例的引用。
# trait Factory {
# type Item<'a> where Self: 'a;
# fn create(&self) -> Self::Item<'_>;
# }
#
struct HelloWorld;
impl Factory for HelloWorld {
type Item<'a> = &'a str;
fn create(&self) -> &str {
"Hello, world!"
}
}
struct HelloName {
name: String,
}
impl Factory for HelloName {
type Item<'a> = String;
fn create(&self) -> String {
let Self { name } = self;
format!("Hello, {name}!")
}
}
struct HelloPrecomputed(String);
impl Factory for HelloPrecomputed {
type Item<'a> = &'a str;
fn create(&self) -> &str {
&self.0
}
}
然而,泛型关联类型的选择让我们对于任何给定的类型都锁定了一个Factory
的实现。假设我们想要一个Context
对象,该对象为Foo
和Bar
实现Factory
。因为Item
是一个关联类型,无论是泛型还是非泛型,我们无法为同一个Context
分别实现Factory
为Foo
和Bar
。
# trait Factory {
# type Item<'a> where Self: 'a;
# fn create(&self) -> Self::Item<'_>;
# }
#
# struct Foo;
# struct Bar;
#
struct Context;
impl Factory for Context {
type Item<'a> = Foo;
fn create(&self) -> Foo {
Foo
}
}
// ERROR: conflicting implementations of trait `Factory` for type `Context`
impl Factory for Context {
type Item<'a> = Bar;
fn create(&self) -> Bar {
Bar
}
}
也无法声明一个函数参数的界限,该界限说明类型实现了Factory
为Foo
和Bar
。
# trait Factory {
# type Item<'a> where Self: 'a;
# fn create(&self) -> Self::Item<'_>;
# }
#
# struct Foo;
# struct Bar;
#
fn use_foo_and_bar<C>(context: C)
where
for <'a> C: Factory<Item<'a> = Foo> + Factory<Item<'a> = Bar>,
{
// ERROR: type annotations needed; cannot satisfy
// `<C as factory>::Item<'_> == Foo`
let foo: Foo = context.create();
let bar: Bar = context.create();
}
然而,使用非泛型关联类型,该类型具有自己的泛型关联类型,可以绕过这些限制
use liftor::Liftor;
use liftor::Owned;
use liftor::Ref;
trait Factory<'a> {
type Item: Liftor<'a>;
fn create(&self) -> <Self::Item as Liftor<'a>>::In<'_>;
}
struct HelloWorld;
impl<'a> Factory<'a> for HelloWorld {
type Item = Ref<str>;
fn create(&self) -> &str {
"Hello, world!"
}
}
struct HelloName {
name: String,
}
impl<'a> Factory<'a> for HelloName {
type Item = Owned<String>;
fn create(&self) -> String {
let Self { name } = self;
format!("Hello, {name}!")
}
}
struct HelloPrecomputed(String);
impl<'a> Factory<'a> for HelloPrecomputed {
type Item = Ref<str>;
fn create(&self) -> &str {
&self.0
}
}
let hello_world = HelloWorld;
let hello_name = HelloName { name: String::from("Alice") };
let hello_precomputed = HelloPrecomputed("Hello, Bob!".to_owned());
assert_eq!(hello_world.create(), "Hello, world!");
assert_eq!(hello_name.create(), "Hello, Alice!");
assert_eq!(hello_precomputed.create(), "Hello, Bob!");
示例 1:带有回调的工厂
您也可以创建自己的Liftor
实现。例如,您可能有一个借用字符串的结构体。您可以为此结构体实现Liftor
以允许重新参数化它所借用的字符串的生命周期。
use liftor::Liftor;
struct Contact<'a> {
name: &'a str,
phone: &'a str,
}
impl<'a> Liftor<'a> for Contact<'a> {
// Remap the lifetime.
type In<'b> = Contact<'b> where 'a: 'b;
}
// An abstraction over a factory that provides an item to a callback. The
// item is characterized by a Liftor rather than a concrete type so that the
// item doesn't have to necessarily outlive the Factory. This allows the
// item that is passed to the callback to borrow locally-owned data.
//
// Implementations can use Owned<T> as the Item parameter to pass an item
// with a 'static lifetime by value, or Ref<T> to pass an item by reference.
trait Factory<'outer, Item: Liftor<'outer>> {
fn acquire<R, Cb>(self, cb: Cb) -> R
where
for<'inner> Cb: FnOnce(Item::In<'inner>) -> R;
}
struct ContactFactory;
impl<'a> Factory<'a, Contact<'a>> for ContactFactory {
fn acquire<R, Cb>(self, cb: Cb) -> R
where
// Note: lifetime for Contact is elided, but thanks to the Liftor and
// its use in the Factory trait, the lifetime of the Contact sent to
// the callback is not necessarily 'a, and can be a lifetime that is
// local to the scope of `acquire`.
Cb: FnOnce(Contact) -> R,
{
// Locally-owned strings.
let name = String::from("John Doe");
let phone = String::from("555-5555");
// Construct a Contact that borrows the locally-owned strings.
let contact = Contact {
name: &name,
phone: &phone,
};
// "Returns" the Contact by passing it to the callback. If we had
// tried to return it normally without callback-passing style, the
// strings would have been dropped and the Contact would have been
// left with dangling references, which would have caused a
// borrow-checker error.
cb(contact)
}
}
ContactFactory.acquire(|contact| {
assert_eq!(contact.name, "John Doe");
assert_eq!(contact.phone, "555-5555");
});
在这个示例中,Factory
特质允许通过延续传递风格(CPS)来构建给定的类型。`acquire`方法接受一个回调,该回调接受任何生命周期的`Contact`,这使得工厂可以传递一个借用本地拥有的字符串的`Contact`。在ad-hoc CPS中,可以像在`ContactFactory`实现中看到的那样使用HRTBs,但无法在没有使用`Liftor`的情况下抽象出一个允许其他借用数据方式(如传递对`Contact`的引用或可变引用)的`Factory`特质。
示例 2:单链表
作为另一个示例,考虑一个使用链表中的下一个节点引用的链表实现
use liftor::Liftor;
#[derive(Debug, PartialEq)]
enum List<'a, T> {
Empty,
Cons(T, &'a List<'a, T>),
}
use List::*;
impl<'a, T> Liftor<'a> for List<'a, T> {
type In<'b> = List<'b, T> where 'a: 'b;
}
trait Factory<'outer, Item: Liftor<'outer>> {
fn acquire<R, Cb>(self, cb: Cb) -> R
where
for<'inner> Cb: FnOnce(Item::In<'inner>) -> R;
}
struct ExampleListFactory;
impl Factory<'static, List<'static, i32>> for ExampleListFactory {
fn acquire<R, Cb>(self, cb: Cb) -> R
where
for<'inner> Cb: FnOnce(List<'inner, i32>) -> R,
{
let list = Cons(1, &Cons(2, &Empty));
cb(list)
}
}
struct StringListFactory;
// A List of borrowed data is parameterized by the lifetime of the data and
// the lifetime of reference to the next node in the list. Because the
// Liftor implementation for List only deals with the latter lifetime, we
// define a new custom liftor type that maps a given lifetime to a List that
// uses that lifetime for both the data and the reference to the next node.
struct StringList;
impl<'a> Liftor<'a> for StringList {
type In<'b> = List<'b, &'b str> where 'a: 'b;
}
impl<'a> Factory<'static, StringList> for StringListFactory {
fn acquire<R, Cb>(self, cb: Cb) -> R
where
for<'inner> Cb: FnOnce(List<'inner, &'inner str>) -> R,
{
let a = String::from("a");
let b = String::from("b");
let c = String::from("c");
cb(Cons(a.as_str(), &Cons(b.as_str(), &Cons(c.as_str(), &Empty))))
}
}
ExampleListFactory.acquire(|list| {
assert_eq!(list, Cons(1, &Cons(2, &Empty)));
});
StringListFactory.acquire(|list| {
assert_eq!(list, Cons("a", &Cons("b", &Cons("c", &Empty))));
});
在这里,我们看到提升器真正发光的地方。`List`类型是一个递归类型,它借用了链表中的下一个节点。因此,不可能返回一个借用本地拥有的数据的`List`,但我们可以使用延续传递风格将`List`传递给回调。`Factory`特质允许以泛型方式表达获取借用数据的类型的方法,而`Liftor`特质表达了获取类型的具体类型是如何通过生命周期参数化的。
Liftor
的实现最适用于高阶函数,用于参数化泛型参数的生命周期,特别是在CPS中,此时借用数据的生命周期在定义时是未知的。