```rust use degeneric_macros::{Degeneric}; use std::marker::PhantomData;
use traitset::traitset; use typed_builder::TypedBuilder;
trait_set!(trait FactoryFn
/// This is doc for ContainerTrait!
struct Container
fn my_fact() -> String { format!("hello world!") }
let c = Container::builder().a(myfact).b(true).c(20).build(); dosomething(&c); accessinnertypes(&c);
fn dosomething(c: &impl ContainerTrait) {}
fn accessinnertypes
Degeneric is a utility library that solves the common problem of having too many generics.
Let's say we want to construct a dependency container like this:
```rust
struct Container
let container = Container { logger: String::from("logger"), client: String::from("http"), };
acceptscontainer(container);
// now to consume such a container, one needs to write the function like this:
fn acceptscontainer
This creates a problem of ever growing list of generics in all functions that touch the container and pollutes APIs with unnecessary generics.
Degeneric proposes solution to this problem by creating a trait and stuffing all of the generic types into the trait as associated types. Instead of the pattern above, you'll end up with this: ```rust use degeneric_macros::Degeneric;
struct Container
let c = Container { logger: String::from("logger"), client: String::from("http"), };
acceptscontainer(c); fn acceptscontainer(c: impl ContainerTrait) {} ```
How is this different, you ask? Instead of accepting a whole lot of generic arguments, I can now write
the function without even using angular brackets and I think that's beautiful.
What is even more beautiful is that you can add more generics without having to modify the
signature of accepts_container
.
```rust use std::borrow::Cow; use std::fmt::Debug;
use degenericmacros::{Degeneric}; use typedbuilder::TypedBuilder;
struct Container<'a, T: 'a + PartialEq
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);
} ```
If you're into hiding generics, you'll be surprised that the galemu crate makes it possible to hide even lifetimes!
The way this example works is, that your Container contains an impl GCon. This object is able
to produce [galemu::Bound
]<GCon::Transaction>
.
The particular implementation of GTran
is provided by [galemu::create_gal_wrapper_type
].
One must manually implement GTran on it.
In principle, galemu lifts the lifetime of Transaction<'a>
into the [galemu::BoundExt
] trait.
The lifetime inference happens in Connection::transaction
. At that point, it's apparent that
the connection's lifetime is passed to Transaction.
```rust use std::fmt::Debug; use std::borrow::Cow; use std::ops::Deref;
use degeneric_macros::Degeneric;
use galemu::{Bound, BoundExt, creategalwrapper_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>); }
creategalwrapper_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
struct Container
let conn = Connection { count : 0 };
let cont = Container { conn, };
fn committransaction(mut c: impl ContainerTrait) { let conn = c.connmut(); let tran = conn.create_transaction(); GTran::commit(tran); }
commit_transaction(cont); ```
```rust use degeneric_macros::{Degeneric}; use std::fmt::Debug;
struct Container
let c = Container { item: vec![""], };
fn constructdefaultvalue
constructdefaultvalue(c);
```
The no_getter
attribute can be used to skip generating a getter.
```compilefail use degenericmacros::{Degeneric};
struct Container<'a, T: 'a, S: 'a> {
item: &'a T,
item2: S,
#[degeneric(no_getter)]
dt: PhantomData,
}
let c = Container {
item: "hello",
item2: format!("this won't have getter!"),
dt: PhantomData,
};
fn acceptcontainer
Some fields may have mutable getters, some not. Degeneric recognizes immutable pointers and references and skips generating mutable getter for them.
```rust use degeneric_macros::{Degeneric};
struct Container<'a, T> { x: &'a T, y: T, }
let c = Container { x: &(), y: (), };
fn acceptcontainer<'a>(mut c: impl Something<'a>) { // OK c.x(); c.y(); c.ymut(); } ```
```compilefail use degenericmacros::{Degeneric};
struct Container<'a, T> { x: &'a T, }
let c = Container { x: &(), };
fn acceptcontainer<'a>(c: impl Something<'a>) { // ERROR: x is a reference which can't be made mut c.xmut(); } ```
Here are some examples of the supported attributes:
#[degeneric(trait_decl_attr = "#[doc = \"Trait declaration\"]")]
#[degeneric(trait_impl_attr = "#[doc = \"Trait implementation\"]")]
#[degeneric(getter_decl_impl_attr = "#[doc = \"Getter declaration & implementation\"])]
#[degeneric(mut_getter_decl_attr = "#[doc = \"Mutable Getter declaration\"])]
```compilefail use degenericmacros::Degeneric;
struct Container
// this will error because the Something trait exists only in the foo configuration
fn accept_container(c: impl Something) {} ```
License: MIT