Crates.io | chassis-proc-macros |
lib.rs | chassis-proc-macros |
version | 0.2.0 |
source | src |
created_at | 2023-09-15 19:28:12.059367 |
updated_at | 2023-09-15 19:28:12.059367 |
description | Procedural Macros for chassis crate |
homepage | |
repository | https://github.com/R1tschY/chassis |
max_upload_size | |
id | 973897 |
size | 44,032 |
Compile-time dependency injector for Rust inspired by Dagger 2
std::sync::Arc
std::any::Any
references) -
At the moment relying on compiler optimization until min_specialization
is stabilized.min_specialization
,
const_type_id
and const_cmp_type_id
Add chassis
to your crate dependencies
[dependencies]
chassis = "^0.2.0"
Structs will be modules that can provide dependencies with functions and that itself can have dependencies. Note: Currently only associated functions are supported!
#[derive(Default)]
pub struct Module;
#[chassis::module]
impl Module {
pub fn provide_something(dep1: Dependency1, dep2: Dependency2) -> Dependency3 {
Dependency3::new(dep1, dep2)
}
// ...
}
Traits will be components. For each trait an implemented component will be created.
The generated implementation will have a Impl
suffix, for example ComponentImpl
. Also a
Component::new
function is created.
#[chassis::injector(modules = [Module])]
pub trait Component {
fn resolve_main_class(&self) -> MainClass;
}
use std::rc::Rc;
// define your business logic
/// printer trait
pub trait Printer {
fn print(&self, input: &str);
}
/// a printer implementation
pub struct StdoutPrinter;
impl Printer for StdoutPrinter {
fn print(&self, input: &str) {
println!("{}", input);
}
}
/// greeter for messages
pub struct Greeter {
message: String,
printer: Rc<dyn Printer>,
}
impl Greeter {
/// constructor with dependencies
pub fn new(message: String, printer: Rc<dyn Printer>) -> Self {
Self { message, printer }
}
/// your business logic
pub fn say_hello(&self) {
self.printer.print(&self.message);
}
}
/// module that is parsed to create the dependency injection code
#[derive(Default)]
pub struct DemoModule;
// use strong types when in need to distinguish
pub struct Message(String);
/// Define how to create your dependencies
#[chassis::module]
impl DemoModule {
pub fn provide_printer() -> Rc<dyn Printer> {
Rc::new(StdoutPrinter)
}
pub fn provide_message() -> Message {
Message("Hello World".to_string())
}
pub fn provide_greeter(
message: Message,
printer: Rc<dyn Printer>
) -> Greeter {
Greeter::new(message.0, printer)
}
}
/// Define which dependencies you need.
///
/// A struct `DemoComponentImpl` will be created for
/// you which implements `DemoComponent`.
#[chassis::injector(modules = [DemoModule])]
pub trait DemoComponent {
/// request the to create injection code for our main class `Greeter`
fn resolve_greeter(&self) -> Greeter;
}
fn main() {
// use generated component implementation
let injector = <dyn DemoComponent>::new()
.expect("DI container should be consistent");
// Resolve main dependency
// Note: it can not fail at runtime!
let greeter = injector.resolve_greeter();
// enjoy!
greeter.say_hello();
}
Normally for every needed dependency the provider function on the module is called. This results
in types created multiple times. This is maybe not intended. The solution is to use a
singleton
attribute. The provide method will than only called once at build time of the
component (call to ComponentImpl::new
). The requirement is that the type implements the
Clone
trait. It is recommendable to use a shared reference type like Rc
or Arc
for
singletons so that really only one instance is created.
#[chassis::module]
impl Module {
#[chassis(singleton)]
pub fn provide_printer() -> Rc<dyn Printer> {
Rc::new(StdoutPrinter)
}
}
'static
)&MyType
when MyType
is provided by a module)Result
in module)Licensed under either of
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.