Code Monkey home page Code Monkey logo

syrette's Introduction

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.

syrette's People

Contributors

hampusmat avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

syrette's Issues

Make tests not return Results

Having a test return a Result makes it a major pain the butt to find out were Errs occur. No line number is printed as it is when unwrap or expect is used.

Example prevent-circular: Cannot resolve binding

Version: 0.4.1
Problem:

Error: BindingResolveFailed { reason: ResolveFailed { reason: BindingResolveFailed { reason: ResolveFailed { reason: BindingResolveFailed { reason: DetectedCircular { dependency_history: DependencyHistory { inner: ["creditor::Foo", "creditor::Bar", "creditor::Foo"] } }, interface: "creditor::Foo" }, affected: "creditor::Bar" }, interface: "creditor::Bar" }, affected: "creditor::Foo" }, interface: "creditor::Foo" }

How to reproduce: This is the code I copied exactly from the example (https://git.hampusmat.com/syrette/tree/examples/prevent-circular)

#![deny(clippy::all)]
#![deny(clippy::pedantic)]
#![allow(clippy::disallowed_names)]
use std::error::Error;

use syrette::di_container::blocking::prelude::*;
use syrette::injectable;
use syrette::ptr::TransientPtr;

struct Foo
{
    bar: TransientPtr<Bar>,
}

#[injectable]
impl Foo
{
    fn new(bar: TransientPtr<Bar>) -> Self
    {
        Self { bar }
    }
}

struct Bar
{
    foo: TransientPtr<Foo>,
}

#[injectable]
impl Bar
{
    fn new(foo: TransientPtr<Foo>) -> Self
    {
        Self { foo }
    }
}

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

    di_container.bind::<Foo>().to::<Foo>()?;
    di_container.bind::<Bar>().to::<Bar>()?;

    let foo = di_container.get::<Foo>()?.transient()?;

    Ok(())
}

Remove DI container interface traits

The traits IDIContainer and IAsyncDIContainer should be removed. They were created so that DIContainer and AsyncDIContainer can be mocked but it's much more ergonomic to just replace the whole struct. I don't know why i didn't do it that way.

Async constructors

Add support for async constructors when the async crate feature is enabled.

IFactory is useless

The IFactory trait is completely useless. AnyFactory can just be downcasted to CastableFactory. This would completely remove the need for the factory attribute macro.

Add specifying constructor function

Currently, constructors are expected to be functions called "new".

A flag could be added to the injectable macro that overrides this.

It would look like this

#[injectable(IFoo, constructor = create)]
impl Foo
{
    fn create(bar: TransientPtr<dyn IBar>) -> Self
    {
        Self { bar }
    }
}

Find a way to get rid of Linkme dependency

It would be splendid if the dependency on Linkme was gotten rid of. This would make Syrette more portable.

However, more investigating needs to be made in order to know if this is even feasible.

Add tagged bindings

This would make a type only be resolved if certain properties has specific values. Like naming it, but with arbitrary stuff.

Types could maybe a problem.

Remove need to explicitly make injectables async compatible

The need to explicitly make injectables async compatible should be removed.

This would be simple if not for the fact that having the async crate feature enable this by default would make it have a non-additive change. More about that here.

An alternative approach to this would be to only implement AsyncInjectable when the type, dependencies and interface are Send + Sync + 'static. This however is not possible (as far as i know) as long as trivial constraints is not stabilized. And even if it becomes stabilized, using it would break the 1.62.1 MSRV.

More research needs to be done about this

Implement Injectable & AsyncInjectable for types implementing Default

The Injectable and AsyncInjectable traits can be implemented for all types which implement the Default trait.

This could look something like this:

impl<T: Default, DIContainerT> Injectable<DIContainerT> for T
{
    fn resolve(
        _: &DIContainerT,
        _: DependencyHistory,
    ) -> Result<TransientPtr<Self>, InjectableError>
    {
        Ok(TransientPtr::new(Self::default()))
    }
}

Add support for stack allocation of injectables

Support for stack allocation of Injectables could probably be implemented.

Stack allocation is to be possible if the interface is a concrete type.

This will require

  • Container functions like get to be able to return either a heap allocated value (like Box) or a stack allocated value.
  • The generated resolve method of Injectables to figure out whether dependencies should be used as heap or stack values
  • Very likely even more

Improve macro errors

Currently, error handling in macros is basically just the use of .unwrap().

This has worked fine but produces unclear error messages. So it should be improved on.

One alternative is using the proc-macro-error crate.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.