#di #ioc #inversion-of-control #dependency-injection

macro syrette_macros

Macros for Syrette, the convenient dependency injection framework

10 releases

0.5.1 Oct 1, 2023
0.5.0 Sep 21, 2023
0.4.2 Nov 28, 2022
0.4.1 Oct 30, 2022
0.1.1 Jul 22, 2022

#932 in Procedural macros

Download history 4/week @ 2023-12-15 12/week @ 2023-12-22 2/week @ 2023-12-29 9/week @ 2024-01-05 2/week @ 2024-01-12 1/week @ 2024-01-19 5/week @ 2024-02-02 19/week @ 2024-02-09 22/week @ 2024-02-16 35/week @ 2024-02-23 39/week @ 2024-03-01 44/week @ 2024-03-08 38/week @ 2024-03-15 42/week @ 2024-03-22 78/week @ 2024-03-29

206 downloads per month
Used in 2 crates (via syrette)

MIT/Apache

130KB
3.5K SLoC

Syrette

Latest Version Documentation Build Coverage Rust

The convenient dependency injection & inversion of control framework for Rust.

Namesake

From the syrette Wikipedia article.

A syrette is a device for injecting liquid through a needle. It is similar to a syringe except that it has a closed flexible tube (like that typically used for toothpaste) instead of a rigid tube and piston.

Features

  • A dependency injection and inversion of control container
  • Autowiring dependencies
  • API inspired from the one of InversifyJS
  • Helpful error messages
  • Supports generic implementations & generic interface traits
  • Binding singletons
  • Injection of third-party structs & traits
  • Named bindings
  • Async factories

Optional features

  • factory. Binding factories (Rust nightly required)
  • prevent-circular. Detection and prevention of circular dependencies. (Enabled by default)
  • async. Asynchronous support

To use these features, you must enable it in Cargo.

Why inversion of control & dependency injection?

The reason for practing IoC and DI is to write modular & loosely coupled applications.

This is what we're trying to avoid:

impl Foo
{
    /// ❌ Bad. Foo knows the construction details of Bar.
    pub fn new() -> Self
    {
        Self {
            bar: Bar::new()
        }
    }

The following is better:

impl Foo
    /// ✅ Better. Foo is unaware of how Bar is constructed.
    pub fn new(bar: Bar) -> Self
    {
        Self {
            bar
        }
    }
}

This will however grow quite tiresome sooner or later when you have a large codebase with many dependencies and dependencies of those and so on. Because you will have to specify the dependencies someplace

let foobar = Foobar::new(
    Foo:new(
        Woof::new(),
        Meow::new()),
    Bar::new(
        Something::new(),
        SomethingElse::new(),
        SomethingMore::new()
    )
)

This is where Syrette comes in.

Motivation

Other DI & IoC libraries for Rust are either unmaintained (di for example), overcomplicated and requires Rust nightly for all functionality (anthill-di for example) or has a weird API (teloc for example).

The goal of Syrette is to be a simple, useful, convenient and familiar DI & IoC library.

Example usage

use std::error::Error;

use syrette::injectable;
use syrette::DIContainer;
use syrette::ptr::TransientPtr;

trait IWeapon
{
	fn deal_damage(&self, damage: i32);
}

struct Sword {}

#[injectable(IWeapon)]
impl Sword
{
	fn new() -> Self
	{
		Self {}
	}
}

impl IWeapon for Sword
{
	fn deal_damage(&self, damage: i32)
	{
		println!("Sword dealt {} damage!", damage);
	}
}

trait IWarrior
{
	fn fight(&self);
}

struct Warrior
{
	weapon: TransientPtr<dyn IWeapon>,
}

#[injectable(IWarrior)]
impl Warrior
{
	fn new(weapon: TransientPtr<dyn IWeapon>) -> Self
	{
		Self { weapon }
	}
}

impl IWarrior for Warrior
{
	fn fight(&self)
	{
		self.weapon.deal_damage(30);
	}
}

fn main() -> Result<(), Box<dyn Error>>
{
	let mut di_container = DIContainer::new();

	di_container.bind::<dyn IWeapon>().to::<Sword>()?;

	di_container.bind::<dyn IWarrior>().to::<Warrior>()?;

	let warrior = di_container.get::<dyn IWarrior>()?.transient()?;

	warrior.fight();

	println!("Warrior has fighted");

	Ok(())
}

For more examples see the examples folder.

Terminology

Transient
A type or trait that is unique to owner.

Singleton
A type that only has a single instance. The opposite of transient. Generally discouraged.

Interface
A type or trait that represents a type (itself in the case of it being a type).

Factory
A function that creates new instances of a specific type or trait.

Default factory
A function that takes no arguments that creates new instances of a specific type or trait.

Rust version requirements

Syrette requires Rust >= 1.62.1 to work. This is mainly due to the dependency on Linkme.

Todo

  • Add support for generic factories

Contributing

You can reach out by joining the mailing list.

This is the place to submit patches, feature requests and to report bugs.


lib.rs:

Macros for the Syrette crate.

Dependencies

~0.7–1.7MB
~33K SLoC