Expand description
§aerosol
Simple but powerful dependency injection for Rust.
This crate provides the Aero
type, which stores dependencies (called resources) keyed by their
type. Resources can be constructed eagerly at application startup, or on-demand when they are
first needed. Resources can access and/or initialize other resources on creation.
The crate will detect dependency cycles (if constructing resource A requires resource B which itself requires resource A) and will panic rather than stack overflow in that case.
The Aero
type has an optional type parameter to make certain resources required. When
a resource is required it can be accessed infallibly. The Aero![...]
macro exists to
easily name an Aero
with a specific set of required resources.
Cloning or type casting an Aero
type is cheap (equivalent to cloning an Arc
).
§Optional features
§async
Allows resources to be constructed asynchrously, and provides a corresponding
AsyncConstructibleResource
trait.
§axum
Provides integrations with the axum
web framework. See the axum
module
for more information.
§Example usage
use std::{sync::Arc, any::Any};
use aerosol::{Aero, Constructible};
// Here, we can list all the things we want to guarantee are in
// our app state. This is entirely optional, we could also just
// use the `Aero` type with default arguments and check that
// resources are present at runtime.
type AppState = Aero![
Arc<PostmarkClient>,
Arc<dyn EmailSender>,
ConnectionPool,
MessageQueue,
MagicNumber,
];
fn main() {
let app_state: AppState = Aero::new()
// Directly add a resource which doesn't implement `Constructible`.
.with(MagicNumber(42))
// Construct an `Arc<PostmarkClient>` resource in the AppState
.with_constructed::<Arc<PostmarkClient>>()
// Check that an implementation of `EmailSender` was added as a result
.assert::<Arc<dyn EmailSender>>()
// Automatically construct anything else necessary for our AppState
// (in this case, `ConnectionPool` and `MessageQueue`)
.construct_remaining();
// Add an extra resource
app_state.insert("Hello, world");
run(app_state);
}
fn run(app_state: AppState) {
// The `get()` method is infallible because the `Arc<dyn EmailSender>` was
// explicitly listed when defining our `AppState`.
let email_sender: Arc<dyn EmailSender> = app_state.get();
email_sender.send(/* email */);
// We have to use `try_get()` here because a `&str` is not guaranteed to
// exist on our `AppState`.
let hello_message: &str = app_state.try_get().unwrap();
println!("{hello_message}");
// ... more application logic
}
// The `Constructible` trait can be implemented to allow resources to be automatically
// constructed.
impl Constructible for PostmarkClient {
type Error = anyhow::Error;
fn construct(aero: &Aero) -> Result<Self, Self::Error> {
PostmarkClient::new(/* initialize using environment variables */)
}
fn after_construction(this: &(dyn Any + Send + Sync), aero: &Aero) -> Result<(), Self::Error> {
// We can use this to automatically populate extra resources on the context.
// For example, in this case we can make it so that if an `Arc<PostmarkClient>` gets
// constructed, we also provide `Arc<dyn EmailSender>`.
if let Some(arc) = this.downcast_ref::<Arc<Self>>() {
aero.insert(arc.clone() as Arc<dyn EmailSender>)
}
Ok(())
}
}
impl Constructible for ConnectionPool {
type Error = anyhow::Error;
fn construct(aero: &Aero) -> Result<Self, Self::Error> {
// ...
}
}
impl Constructible for MessageQueue {
type Error = anyhow::Error;
fn construct(aero: &Aero) -> Result<Self, Self::Error> {
// ...
}
}
§Implementation details
The Aero
type manages shared ownership of a map from resource types to “slots”.
For a given resource type, the corresponding “slot” can be in one of three state:
- Absent. No instance of this resource is present in the map.
- Present. An instance of this resource exists in the map and can be accessed immediately.
- Under construction. An instance of this resource is currently under construction, and may be accessed once construction has finished. The slot maintains a list of threads or tasks waiting for this resource to be constructed, and will wake them when the resource becomes available.
Resources can be constructed synchronously, or (when the feature is enabled) asynchronously.
If a resource is accessed whilst under construction, the caller will wait for construction
to complete. The caller determines whether the wait occurs synchronously or asynchronously,
depending on whether obtain()
or obtain_async()
is used.
It is possible (and allowed) for a thread to synchronously wait on a resource being constructed asynchronously in a task, or for a task to asynchronously wait on a resource being synchronously constructed on a thread.
Re-exports§
pub use frunk;
Modules§
- Integration with the
axum
web framework.
Macros§
- Define a custom
Aero
alias with a specific set of required types
Structs§
- Stores a collection of resources keyed on resource type. Provides methods for accessing this collection. Can be cheaply cloned.
Traits§
- Implemented for values which can be constructed asynchronously from other resources. Requires feature
async
. - Implemented for resources which can be asynchronously constructed from other resources. Requires feature
async
. Do not implement this trait directly, instead implementAsyncConstructible
and ensure the remaining type bounds are met for the automatic implementation ofAsyncConstructibleResource
. - Automatically implemented for resource lists where every resource can be asynchronously constructed.
- Implemented for values which can be constructed from other resources.
- Implemented for resources which can be constructed from other resources. Do not implement this trait directly, instead implement
Constructible
and ensure the remaining type bounds are met for the automatic implementation ofConstructibleResource
. - Automatically implemented for resource lists where every resource can be constructed.
- Automatically implemented for values which can be indirectly asynchronously constructed from other resources. Requires feature
async
. - Automatically implemented for values which can be indirectly constructed from other resources.
- Bound on the types that can be used as an aerosol resource.
- A compile-time list of resource types which are statically guaranteed to be present.