Lifetimes and interfaces
Choose between Singleton, Scoped, and Transient, and resolve services through their interfaces.
A lifetime determines when a service instance is created, who shares it, and who cleans it up. Specify lifetimes explicitly in your examples.
Choose a lifetime#
| Lifetime | Created | Shared with | Cleaned up |
|---|---|---|---|
Singleton |
During container build | All work in the container | At container shutdown |
Scoped |
On first resolution in the scope | The same request or unit of work | At scope shutdown |
Transient |
On every resolution | Not shared | By the resolving scope; by the container if there is no scope |
If #[service] does not specify a lifetime, the default is Transient. Application resources such as connection pools can be Singleton, while a request's transaction context can be Scoped.
Dependency lifetimes#
A Singleton cannot retain a Scoped service. Inserting a Transient service in between does not remove the need for a scope: there is no active scope during startup. A direct lifetime error and an indirect resolution error do not necessarily arise in the same validation phase.
Singleton → Transient is allowed. However, that instance retained by the Singleton is tied to the root lifetime. It does not become a new instance on every use.
A scope is not an Arc counter. Moving a Scoped service's
Arcoutside the request does not extend the scope's lifetime. Open a separate scope for each independent unit of work.
Inject through an interface#
You can use a service through a behavioral contract instead of its concrete type. In the following snippet, Greeting is an application-defined trait, and GreetingService is its service implementing ServiceTrait:
#[service(interface = dyn Greeting, lifetime = "Singleton")]
// Use alongside #[derive(Injectable)] on GreetingService.
#[inject]
greeting: Arc<dyn Greeting>,In an action, Service<dyn Greeting> resolves through the same interface registration. More than one active implementation of the same interface causes a build error. Concrete and interface resolutions follow the registered lifetime; for Transient, two resolutions do not share the same allocation.
Seed an instance in tests#
ApplicationContainer::builder().seed_singleton(value) replaces the initial instance of an already registered Singleton. It does not add a new service registration. An unknown type, a Scoped/Transient type, or repeated seeding of the same type is rejected.
Missing dependencies, cycles, duplicate interfaces, and lifetime errors are handled at startup. Use the application's existing provider instead of rebuilding the container inside an endpoint.
Sources for this page v0.1.0
This guide is based on the reviewed framework commit.