dmitryb-dev
waiter
Rust

Dependency injection, Inversion of control container for rust with compile time binding.

Last updated Feb 28, 2026
87
Stars
8
Forks
2
Issues
0
Stars/day
Attention Score
12
Language breakdown
Rust 100.0%
โ–ธ Files click to expand
README

Dependency injection for Rust

How to use:

Cargo.toml:

waiter_di = "1.6.5"
lib.rs or any other file, that uses library:
use waiter_di::*;

See examples/1get_started.rs for minimal example of usage.

See examples/2_modules.rs for example with modules and constructors.

See examples/3injectoptions_list.rs for the demo of all available injection options.

How to use

Annotate structure with #[component]

#[component]
struct Comp {}

Annotate impl blocks with #[provides]

#[provides]
impl Interface for Comp {}

Create a container:

fn main() {
    let mut container = Container::<profiles::Default>::new();
}

Get dependency ref:

fn main() {
    let comp = Provider::<dyn Interface>::get(&mut container);
}

Inject references

For Rc:

#[component]
struct Dependency;

#[component] struct Comp { dependency_rc: Rc<Dependency> }

fn main() { let mut container = Container::<profiles::Default>::new(); Provider::<Comp>::get(&mut container); }

to use Arc instead of Rc you need to add async feature in cargo:

waiter_di = { version = "...", features = [ "async" ] }

Also, you can use waiter_di::Wrc type that will be compiled to Rc or Arc depending on async feature.

To create new struct instead of getting reference:

#[component]
struct Comp {
    dependency: Dependency,
    dependency_box: Box<Dependency>
}

fn main() { let mut container = Container::<profiles::Default>::new(); Provider::<Comp>::create(&mut container); Provider::<Comp>::create_boxed(&mut container); }

Properties

It uses config crate under the hood, for example it tries to find float_prop in args as --float_prop <value>, if not found it tries to find it in environment variables, after that tries config/{profile}.toml, after that config/default.toml

#[derive(Debug, Deserialize)]
struct ConfigObject {
    i32_prop: i32
}

#[component] struct Comp { config: Config, #[prop("int")] int_prop: usize, #[prop("int")] intpropopt: Option<usize>, #[prop("int" = 42)] intpropwithdefaultvalue: usize, float_prop: f32, #[prop] config_object: ConfigObject }

Dependency cycle

Use Deferred type:

#[component]
struct Comp {
    dependency_def: Deferred<Dependency>,
    dependencydefrc: Deferred<Rc<Dependency>>,
    dependencydefbox: Deferred<Box<Dependency>>
}

Profiles

You can use predefined profiles from waiter_di::profile" or create custom:

<pre><code class="lang-rust">struct CustomProfile;

#[provides(profiles::Dev, CustomProfile)] impl Interface for Comp {}

fn main() { let mut container = Container::&lt;profiles::Default&gt;::new(); let mut container = Container::&lt;profiles::Dev&gt;::new(); let mut container = Container::&lt;CustomProfile&gt;::new(); }</code></pre>

Get profile from args, environment or config/default.toml

Just define property named profile as --profile arg, profile env variable or profile property in config/default.toml and use inject! macro:

<pre><code class="lang-rust">fn main() { let comp = inject!(Comp: profiles::Default, profiles::Dev); }</code></pre>

inject! macro can't be used for several components, so it's recommended to use it with modules:

<pre><code class="lang-rust">#[module] struct SomeModule { component: Component } #[module] struct RootModule { some_module: SomeModule } fn main() { let root_module = inject!(RootModule: profiles::Default, profiles::Dev); }</code></pre>

In this case #[module] is just a synonym for #[component]

Factory functions:

If you can't use #[component] annotation, use factory function instead:

<pre><code class="lang-rust">#[provides] fn createdependency(boolprop: bool) -&gt; Dependency { Dependency { prop: bool_prop } }</code></pre>

To use it like a constructor, use it with #[component] on impl block:

<pre><code class="lang-rust">struct Comp();

#[component] impl Comp { #[provides] fn new() -&gt; Self { Self() } }</code></pre>

Deferred args in factory functions is unsupported. In the rest it can accept the same arg types as #[component].

External types isn't supported for factory functions:

<pre><code class="lang-rust">#[provides] // won&#39;t compile fn createexternaltype_dependency() -&gt; HashMap&lt;i32, i32&gt; { HashMap::new() }</code></pre>

So you need to create crate-local wrapper:

<pre><code class="lang-rust">struct Wrapper(HashMap&lt;i32, i32&gt;);

#[provides] fn createexternaltype_dependency() -&gt; Wrapper { Wrapper(HashMap::new()) }</code></pre>

For convenience, you can use #[wrapper]` attribute to implement Deref automatically:

#[wrapper]
struct HashMap(std::collections::HashMap<i32, i32>);

#[provides] fn createexternaltype_dependency() -> HashMap { return HashMap(std::collections::HashMap::<i32, i32>::new()); }

๐Ÿ”— More in this category

ยฉ 2026 GitRepoTrend ยท dmitryb-dev/waiter ยท Updated daily from GitHub