#泛型 #注入 #依赖注入 #依赖注入 #宏推导

degeneric-macros

将结构泛型隐藏到特质关联类型中

9 个不稳定版本

0.5.1 2023年12月26日
0.5.0 2022年10月9日
0.4.1 2022年10月8日
0.4.0 2022年2月1日
0.1.0 2021年10月27日

#1094 in Rust 模式

每月 31 次下载

MIT 许可证

54KB
811

degeneric-macros

GitHub license GitHub Workflow Status Crates.io Crates.io (latest)

快速入门

use degeneric_macros::{Degeneric};
use std::marker::PhantomData;

use trait_set::trait_set;
use typed_builder::TypedBuilder;

trait_set!(trait FactoryFn<T> = 'static + Send + Sync + Fn() -> T);

#[derive(Degeneric, TypedBuilder)]
#[degeneric(trait = "pub trait ContainerTrait")]
/// This is doc for ContainerTrait!
struct Container<T: Default, A: FactoryFn<T>, B> {
    a: A,
    b: B,
    c: u32,
    #[builder(default)]
    _t: PhantomData<T>,
}

fn my_fact() -> String {
    format!("hello world!")
}

let c = Container::builder().a(my_fact).b(true).c(20).build();
do_something(&c);
access_inner_types(&c);

fn do_something(c: &impl ContainerTrait) {}
fn access_inner_types<C: ContainerTrait>(c: &C) {
    let same_as_a: C::A;
}

简介

问题

Degeneric 是一个工具库,用于解决具有过多泛型的问题。假设我们想要构建一个像这样的依赖容器

struct Container<Logger, HttpClient> {
    logger: Logger,
    client: HttpClient,
    // ...and so on...
}

let container = Container {
    logger: String::from("logger"),
    client: String::from("http"),
};

accepts_container(container);
// now to consume such a container, one needs to write the function like this:
fn accepts_container<Logger, HttpClient>(c: Container<Logger, HttpClient>) {}

这会在所有接触容器的函数中产生一个不断增长的泛型列表,并使 API 被不必要的泛型所污染。

Degeneric 解决方案

Degeneric 通过创建一个特质并将所有泛型类型填充为该特质的关联类型来解决这个问题。而不是上面的模式,你最终会得到这个

use degeneric_macros::Degeneric;

#[derive(Degeneric)]
#[degeneric(trait = "pub trait ContainerTrait")]
struct Container<Logger, HttpClient> {
    logger: Logger,
    client: HttpClient,
}

let c = Container {
    logger: String::from("logger"),
    client: String::from("http"),
};

accepts_container(c);
fn accepts_container(c: impl ContainerTrait) {}

这有什么不同?你可能会问。我现在可以不用尖括号就编写函数,我认为这是美丽的。更美丽的是,你可以添加更多泛型,而无需修改 accepts_container 的签名。

Degeneric 理解生命周期

use std::borrow::Cow;
use std::fmt::Debug;

use degeneric_macros::{Degeneric};
use typed_builder::TypedBuilder;

#[derive(Degeneric, TypedBuilder)]
#[degeneric(trait = "trait ContainerTrait")]
struct Container<'a, T: 'a + PartialEq<i32> + Debug> {
    cow: &'a Cow<'a, str>,
    reference: &'a T,
}

let cow = Cow::Owned(String::from("hello lifetimes"));
{
    let reference = 42;
    let c = Container::builder().cow(&cow).reference(&reference).build();

    fn accept_container<'a>(cont: &impl ContainerTrait<'a>) {
        assert_eq!(cont.cow().as_ref(), "hello lifetimes");
        assert_eq!(cont.reference(), &42_i32);
    }

    accept_container(&c);
}

Degeneric 可以与 galemu 一起使用!

如果你喜欢隐藏泛型,你可能会惊讶地发现 galemu 包可以使隐藏生命周期成为可能!

这个示例的工作方式是,你的 Container 包含一个 impl GCon。该对象能够产生 galemu::Bound<GCon::Transaction>

GTran 的特定实现由 galemu::create_gal_wrapper_type 提供。必须手动在它上面实现 GTran。

原则上,galemu 将 Transaction<'a> 的生命周期提升到 galemu::BoundExt 特质。生命周期推断发生在 Connection::transaction。此时,很明显,连接的生命周期被传递给 Transaction。

use std::fmt::Debug;
use std::borrow::Cow;
use std::ops::Deref;

use degeneric_macros::Degeneric;

use galemu::{Bound, BoundExt, create_gal_wrapper_type};

// begin galemu

struct Connection {
    count: usize
}

struct Transaction<'conn> {
    conn: &'conn mut Connection
}

impl Connection {
    fn transaction(&mut self) -> Transaction {
        Transaction { conn: self }
    }
}

trait GCon {
    type Transaction: GTran;

    fn create_transaction(&mut self) -> Bound<Self::Transaction>;
}

trait GTran: for<'s> BoundExt<'s> {
    fn commit<'s>(me: Bound<'s, Self>);
    fn abort<'s>(me: Bound<'s, Self>);
}

create_gal_wrapper_type!{ struct TransWrap(Transaction<'a>); }

impl GCon for Connection {
    type Transaction = TransWrap;

    fn create_transaction(&mut self) -> Bound<Self::Transaction> {
        let transaction = self.transaction();
        TransWrap::new(transaction)
    }
}

impl GTran for TransWrap {
    fn commit<'s>(me: Bound<'s, Self>) {
        let trans = TransWrap::into_inner(me);
        trans.conn.count += 10;
    }

    fn abort<'s>(me: Bound<'s, Self>) {
        let trans = TransWrap::into_inner(me);
        trans.conn.count += 3;
    }
}

// end galemu

#[derive(Degeneric)]
#[degeneric(trait = "pub trait ContainerTrait")]
struct Container<T: GCon> {
    conn: T,
}

let conn = Connection { count : 0 };

let cont = Container {
    conn,
};

fn commit_transaction(mut c: impl ContainerTrait) {
    let conn = c.conn_mut();
    let tran = conn.create_transaction();
    GTran::commit(tran);
}

commit_transaction(cont);

Degeneric + dynamize

Degeneric 支持动态化生成的特质。这是如何工作的?

这里是一个如何动态化生成的特质的示例

#[derive(Degeneric)]
#[degeneric(dynamize, trait = "pub trait GeneratedContainerTrait")]
struct Container<T: Any> {
    item: T,
}

按照惯例,dynamize生成了一个DynGeneratedContainerTrait,其中的类型是装箱的。请参考dynamize文档以获取更多信息。

Degeneric理解where子句

use degeneric_macros::{Degeneric};
use std::fmt::Debug;

#[derive(Degeneric)]
#[degeneric(trait = "pub trait ContainerTrait")]
struct Container<T> where T: Default + Debug + PartialEq {
    item: T,
}

let c = Container {
    item: vec![""],
};

fn construct_default_value<C: ContainerTrait>(c: C) {
    let v: C::T = Default::default();
    assert_eq!(v, Default::default());
}

construct_default_value(c);


只为某些字段生成getter

可以使用no_getter属性来跳过生成getter。

use degeneric_macros::{Degeneric};

#[derive(Degeneric)]
#[degeneric(trait = "pub(crate) trait Something")]
struct Container<'a, T: 'a, S: 'a> {
    item: &'a T,
    item2: S,
    #[degeneric(no_getter)]
    dt: PhantomData<S>,
}

let c = Container {
    item: "hello",
    item2: format!("this won't have getter!"),
    dt: PhantomData<S>,
};

fn accept_container<C: Something>(c: C) {
    /// ERROR: dt doesn't have a getter!
    assert_eq!(c.dt(), format!("this won't have getter!"));
}

accept_container(c);

Degeneric确定可变性

某些字段可能有可变的getter,某些则没有。Degeneric识别不可变指针和引用,并跳过为它们生成可变的getter。

use degeneric_macros::{Degeneric};
#[derive(Degeneric)]
#[degeneric(trait = "pub(crate) trait Something")]
struct Container<'a, T: 'a> {
    x: &'a T,
    y: T,
}

let mut c = Container {
    x: &(),
    y: (),
};

fn accept_container<'a>(mut c: impl Something<'a>) {
    // OK
    c.x();
    c.y();
    c.y_mut();
}

accept_container(c);
use degeneric_macros::{Degeneric};

#[derive(Degeneric)]
#[degeneric(trait = "pub(crate) trait Something")]
struct Container<'a, T> {
    x: &'a T,
}

let c = Container {
    x: &(),
};

fn accept_container<'a>(c: impl Something<'a>) {
    // ERROR: x is a reference which can't be made mut
    c.x_mut();
}

在所有地方添加属性!

对于某些属性,您只需在字段上添加它们,它们就会自动转发到所有getter。以下是这样属性的列表

  • #[allow]
  • #[doc]
  • #[cfg(...)]
  • #[cfg_attr(...)]

如果您需要更细粒度,您可以将属性仅添加到

  • 特质声明:#[degeneric(trait_decl_attr = "#[doc = \"Trait declaration\"]")]
  • 特质实现块:#[degeneric(trait_impl_attr = "#[doc = \"Trait implementation\"]")]
  • 字段不可变getter实现:#[degeneric(getter_impl_attr = "#[doc = \"Getter implementation\"])]
  • 字段可变getter声明:#[degeneric(mut_getter_decl_attr = "#[doc = \"Mutable Getter declaration\"])]
use degeneric_macros::Degeneric;

#[derive(Degeneric)]
#[degeneric(trait = "pub(crate) trait Something")]
#[degeneric(trait_decl_impl_attr = "#[cfg(foo)]")]
/// This is documentation for the `Something` trait
struct Container<T> {
    x: T,
}

// this will error because the Something trait exists only in the foo configuration
#[cfg(not(foo))]
fn accept_container(c: impl Something) {}

Crates degeneric与以下兼容

CloneExt

除了解决依赖注入问题之外,degeneric还有助于克隆。可能存在您在另一种类型中持有不可克隆类型的情况。在这些情况下,可能可以通过不同方式克隆值。

失败的示例


#[derive(Default)]
struct NonClone;

#[derive(Clone)]
struct Container {
    nc: PhantomData<NonClone>,
}

在这种情况下,可以求助于degeneric的CloneExt derive宏。目前,它提供了一个属性来调整字段克隆的方式

#[derive(Default)]
struct NonClone;

#[derive(Default, degeneric_macros::CloneExt)]
struct Container {
    #[clone_ext(clone_behavior(call_function="Default::default"))]
    nc: NonClone,
}

Container::default().clone();

许可证:MIT

依赖项

~2MB
~44K SLoC