Service Lifetimes
A service can have the following lifetimes:
- Transient - a new instance is created every time it is requested
- Singleton - a single, new instance is created the first time it is requested
- Scoped - a new instance is created once per provider that it is requested from
A service with a Singleton lifetime, which depends on service that has a Transient lifetime will effectively promote that service to a Singleton lifetime as well. A service with a Singleton lifetime, which depends on service that has a Scoped lifetime will result in a validation error.
Lifetime Management
The lifetime of a service determines how long a service lives relative to its owning ServiceProvider
. When a ServiceProvider
is dropped, all of the service instances it owns are also dropped. If a service instance somehow outlives its owning ServiceProvider
, when the last of the owners is dropped, the service will also be dropped. The ServiceProvider
itself will never leak any instantiated services.
There are scenarios where you might want a ServiceProvider
to be scoped for a limited amount of time. A HTTP request, for example, has a Per-Request lifetime where you might want some services to be shared within the scope of the request (e.g. scoped) and then dropped. A new scope can be created via create_scope
from any ServiceProvider
.
Consider the following structures:
use di::*;
#[injectable]
pub struct Bar;
#[injectable]
pub struct Foo {
bar: Ref<Bar>
}
Service Provider Singletons
A Singleton is created exactly once and lives for the lifetime of the root ServiceProvider
no matter where it is actually first instantiated.
A Singleton in the root scope will be the same as a Singleton created in a nested scope.
use crate::*;
use di::*;
let provider = ServiceCollection::new()
.add(Bar::transient())
.add(Foo::singleton())
.build_provider()
.unwrap();
let foo1 = provider.get_required::<Foo>();
{
let scope = provider.create_scope();
let foo2 = scope.get_required::<Foo>();
assert!(Ref::ptr_eq(&foo1, &foo2));
}
In addition, if a Singleton is first created in a nested scoped, it will still be the same instance in the root scope.
use crate::*;
use di::*;
let provider = ServiceCollection::new()
.add(Bar::transient())
.add(Foo::singleton())
.build_provider()
.unwrap();
let foo1;
{
let scope = provider.create_scope();
foo1 = scope.get_required::<Foo>();
}
let foo2 = provider.get_required::<Foo>();
assert!(Ref::ptr_eq(&foo1, &foo2));
Service Provider Scopes
A Scoped service only lives as long as the lifetime of the owning service provider.
use crate::*;
use di::*;
let provider = ServiceCollection::new()
.add(Bar::transient())
.add(Foo::scoped())
.build_provider()
.unwrap();
let foo1 = provider.get_required::<Foo>();
let foo2;
{
let scope = provider.create_scope();
foo2 = scope.get_required::<Foo>();
let foo3 = scope.get_required::<Foo>();
// foo2 == foo3 because they have the same scope
assert!(Ref::ptr_eq(&foo2, &foo3));
} // ← all instances owned by 'scope' are dropped here
// 'foo2' outlived the scope because we held onto it for
// testing/demonstration purposes. as soon as it goes
// out of scope, it will be dropped. transient services
// behave in the same way and no instances are held by
// the ServiceProvider they were resolved from
//
// foo1 != foo2 because they came from different scopes
assert!(!Ref::ptr_eq(&foo1, &foo2));