2 unstable releases
0.2.0 | Nov 25, 2021 |
---|---|
0.1.0 | Jan 5, 2021 |
#20 in #passed
335 downloads per month
Used in teloc
19KB
438 lines
teloc
Teloc is simple, compile-time DI framework for Rust inspired by C# Dependency Injection Framework.
What is DI?
Dependency injection (DI) is a technique in which an object receives other objects that it depends on. These other objects are called dependencies. In the typical "using" relationship the receiving object is called a client and the passed (that is, "injected") object is called a service. The code that passes the service to the client can be many kinds of things and is called the injector. Instead of the client specifying which service it will use, the injector tells the client what service to use. The "injection" refers to the passing of a dependency (a service) into the object (a client) that would use it.
Highlights
- Compile-time - teloc uses the powerful rust type system check for the existence of dependencies that have the proper lifetime at compile-time. This means you cannot compile your code if a required dependency has not been registered or if it's lifetime is shorter to what's requested. If your code compiles, that means it runs!
- Zero-overhead - teloc uses only zero-overhead abstractions such as traits, generics, newtypes and unit types, and compile-time resolving of dependencies, so you don't worry about overhead at runtime.
- Simple API - teloc provides you a simple API with only one struct and one attribute macro needed for working with library.
- Integration with existing enviroment - teloc can be used with any existing frameworks like actix-web, warp, rocket. Now there is only support for actix-web as you can see in the example.
How to use
There are one type can be provider of services: ServiceProvider
. It used as store for dependencies with
Instance
and Singleton
lifetimes, and for declaring all dependencies using .add_*()
methods. It can be forked to
create a local scope with local instances.
There are four lifetimes for dependencies:
Transient
. Service will be created when resolves. Can depend on dependencies with anything lifetime.Singleton
. Service will be created once atServiceProvider
when it resolved (lazy). Can depend on dependencies with anything lifetime. Cannot depend on services from forkedServiceProvider
instances.Instance
. Dependency was created outside ofServiceProvider
and can be used by any other dependency.
How to work:
- Declare your structs.
- Create constructors and add
#[inject]
macro on its. - Create a
ServiceProvider
object. - Add your services and dependencies using
ServiceProvider::add_*
methods. - Fork
ServiceProvider
if you need to create local scope. - Get service from provider using
.resolve()
method. - Work with service.
Example:
use teloc::*;
// Declare your structs
struct ConstService {
number: i32,
}
// #[inject] macro is indicate that dependency can be constructed using this function
#[inject]
impl ConstService {
pub fn new(number: i32) -> Self {
ConstService { number }
}
}
// Derive macro can be used when all fields implement `Dependency` trait, but
// we recommend using the #[inject] macro it in production code instead.
#[derive(Dependency)]
struct Controller {
number_service: ConstService,
}
fn main() {
// Create `ServiceProvider` struct that store itself all dependencies
let sp = ServiceProvider::new()
// Add dependency with `Singleton` lifetime. More about lifetimes see above.
.add_singleton::<ConstService>()
// Add dependency with `Transient` lifetime. More about lifetimes see above.
.add_transient::<Controller>();
// Fork `ServiceProvider`. It creates a new `ServiceProvider` which will have
// access to the dependencies from parent `ServiceProvider`.
let scope = sp
// .fork() method creates a local mutable scope with self parent immutable `ServiceProvider`.
.fork()
// Add an instance of `i32` that will be used when `ConstService` will be initialized.
.add_instance(10);
// Get dependency from `ServiceProvider`
let controller: Controller = scope.resolve();
assert_eq!(controller.number_service.number, 10);
}
For documentation see page on docs.rs.
For more examples see examples folder or tests folder.
Comparison with other DI frameworks
Feature | teloc | shaku | waiter_di |
Compile-time checks | Yes | Yes | Yes |
Can be used without dyn traits | Yes | Yes | Yes |
Many service providers in one app | Yes | Yes | No |
Different lifetimes | Yes | Yes | No |
How to read errors
Sometimes teloc
can give strange large errors. But no panic! You can define your problem by read the manual of reading errors.
Pro tips
This section contains pro tips that you might want to use when working with the library.
Get type of instance of ServiceProvider
It will be useful when you want to store an instance of ServiceProvider
in a struct or return from a function or
pass as an argument.
What you need:
- Paste following code after
ServiceProvider
initialization:let () = service_provider;
. - Compiler will give you very big terrible type starting with
teloc::ServiceProvider<...>
. - Copy that type into type alias, for example
type ConcreteSP = /*compiler output*/;
. - Use
ConcreteSP
when you want writeServiceProvider
instance type. - If you change
ServiceProvider
initialization repeat steps 1-4.
License
Licensed under either of Apache License, Version 2.0 or MIT license at your option.Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
Dependencies
~2MB
~46K SLoC