dose
[![Current Crates.io Version](https://img.shields.io/crates/v/dose.svg)](https://crates.io/crates/dose)
> Your daily dose of structs and functions.
## Features
* Simplicity 👌
* Blazingly Fast 🏎️💨
* Lazy Instantiation 🦥
* Statically Checked 🕵️♀️
* Dependency Injection 💉
* Declarative Interface ✨
* No Runtime Dependency Graph 🎉
Unfortunately we do not support runtime dependency cycle detection ... yet.
## Usage
### Initialization
You need to initialize the crate with the `init!` macro at the root of your project in `lib.rs`.
This is a current limitation of rust and this may become unnecessary in future versions.
```rust
dose::init!();
```
### Registering Providers
You can register providers with the `provide` attribute.
```rust
#[provide]
fn provide_something(context: &mut Context) -> Something {
Something::new(...)
}
```
The `&mut Context` is always passed as argument and can be used to resolve other types.
The config is also available from the context `context.config`.
Each time the type `Something` is `get!`, the function above will be called.
If you want the same instance to be used, the attribute parameter `singleton` needs to be set to `true`.
```rust
#[provide(singleton=true)]
fn provide_something(context: &mut Context) -> Something {
Something::new(...)
}
```
In this case, each time the type `Something` is `get!`, the instance will be cloned.
This really becomes a singleton if type `Something` is wrapped in an `std::sync::Arc` or a `std::rc::Rc`.
```rust
#[provide(singleton=true)]
fn provide_something(context: &mut Context) -> Arc {
Arc::new(Something::new(...))
}
```
### Resolving Instances
The `get!` macro is the way to get the instance of a type.
```rust
let mut context = dose::Context::new(config);
let my_type: MyType = dose::get!(context);
```
This macro can also be used inside providers.
```rust
struct MyType {
a: TypeA,
b: TypeB,
}
#[provide]
fn provide_my_type(context: &mut Context) -> MyType {
MyType {
a: get!(context), // Type infered to TypeA
b: get!(context), // Type infered to TypeB
}
}
```
Note that if the provider of `TypeA` or `TypeB` is not registered, a compilation error will occur.
## Good Practices
### Module Separation
Providers should be defined in modules specifically created for declaring how each type is instantiated.
### Context Lifecycle
The `Context` should be deleted (`drop`) before the application is started.
Using out of scope.
```rust
use dose::{Context, get}
... // declare all modules
fn create_server(config: Config) -> Server {
let mut context = Context::new(config);
get!(context)
}
#[tokio::main]
async fn start_server_implicit() {
let server = create_server(Config::load(...));
// Implicitly drop the context because it becomes out of scope
server.start().await
}
#[tokio::main]
async fn start_server_explicit() {
let mut context = Context::new(config);
let server: Server = get!(context);
// Explicitly drop the context using std::mem::drop
std::mem::drop(context);
server.start().await
}
```
### Providers Single Purpose
Keep providers simple, only creation logic should be in there.
No algorithms or domain logic should be executed.
### Config
The config provided in the context can be anything, but a simple struct without functions and public fields can be used.
Each provider should only require the context and the config to execute properly.