pub trait Provider<S>:
Any
+ Send
+ Sync
+ 'static {
// Provided methods
fn name(&self) -> &'static str { ... }
fn boot_priority(&self) -> Option<u8> { ... }
fn run_priority(&self) -> Option<u8> { ... }
fn order(&self) -> ProviderOrder { ... }
fn boot<'life0, 'life1, 'async_trait>(
&'life0 self,
_state: &'life1 S,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait { ... }
fn validate(&self, _state: &S) -> Result<()> { ... }
fn as_any(&self) -> &dyn Any
where Self: Sized { ... }
fn as_reloadable(&self) -> Option<&dyn Reloadable<S>> { ... }
fn as_finalizable(&self) -> Option<&dyn Finalizable<S>> { ... }
fn as_runnable(self: Arc<Self>) -> Option<Arc<dyn Runnable<S>>> { ... }
}Expand description
Any service that can be registered in the DI registry.
§Lifecycle convention
Each provider lives in four explicit phases. Mixing work across phase boundaries is the most common bug source — keep them strict.
-
register()(free fn, outside the trait) — synchronous, no async, called once during bootstrap. Constructs the provider in a placeholder/empty state and inserts it into the registry.Allowed:
- Read state-level inputs (
state.run_mode(),state.config_dir()) to choose what to register. - Read on-disk config synchronously only if the answer decides
whether to register the provider at all (e.g. feature toggles,
worker pinning). Use
std::fshere — register is sync.
Forbidden:
- Resolving other providers from the registry (they may not exist
yet; ordering is settled by
Provider::order()and coarse priority, not by register order). - Async I/O.
- Spawning tasks.
- Building the operational snapshot (that’s
boot()).
- Read state-level inputs (
-
boot()— async, called after everyregister()ran, in lifecycle order. This is where the provider becomes usable.Allowed / expected:
- Resolve dependencies from the registry — by now every other
register()has run. - Async I/O —
tokio::fsfor config, network calls, etc. Neverstd::fs(it blocks the runtime). - Build the runtime snapshot and publish it via
ArcSwap/ArcSwapOptionso concurrent readers see atomic swaps. - Honor disabled-state from config: leave the snapshot empty and
return
Ok(())rather than failing.
Forbidden:
- Spawning long-running tasks. Boot must return when state is
ready; the long task lives in
Runnable::run().
- Resolve dependencies from the registry — by now every other
-
validate()— synchronous readiness/invariant check after boot and before runnable tasks start. Use this for cheap checks that need boot-published state to exist. -
Runnable::run()— see that trait. The only place for long-lived loops; honors disabled-state by returningOk(())immediately. Graceful teardown of the work started here belongs here too: observe the suppliedRunContextinside the run future, drain, and return — do NOT split that into a separate hook. -
Finalizable::finalize()— optional capability (see that trait), exposed viaas_finalizable(). Best-effort release of non-running resources after the runnable tasks have drained (shm segments, lock files). Not a stop mechanism for runnables.
Reload (Reloadable::reload()) follows the same shape as boot().
Provided Methods§
Sourcefn boot_priority(&self) -> Option<u8>
fn boot_priority(&self) -> Option<u8>
Optional boot priority. Lower values run earlier among otherwise-ready
providers. None means priority::NORMAL. Prefer Provider::order()
for dependency relationships; priority is only a coarse tie-breaker.
Sourcefn run_priority(&self) -> Option<u8>
fn run_priority(&self) -> Option<u8>
Optional runtime task start priority. Lower values run earlier.
None means priority::NORMAL.
Sourcefn order(&self) -> ProviderOrder
fn order(&self) -> ProviderOrder
Optional type-based boot/reload ordering hints.
The registry builds one ordered lifecycle plan and uses it for boot,
validate, finalize, and reload. Reload skips providers that are not
Reloadable, but dependency relationships remain the same: reload is
a boot emulation on a live process.
Sourcefn boot<'life0, 'life1, 'async_trait>(
&'life0 self,
_state: &'life1 S,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn boot<'life0, 'life1, 'async_trait>(
&'life0 self,
_state: &'life1 S,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Bootstrap-time async initialization. See the trait-level lifecycle
convention for what belongs here vs in register() / run().
Default no-op so providers that only need register() insertion
don’t have to implement this.
Sourcefn validate(&self, _state: &S) -> Result<()>
fn validate(&self, _state: &S) -> Result<()>
Synchronous readiness validation after boot() and before runnable
tasks are spawned.
Use this for cheap checks over boot-published state, missing files, or conflicting settings that should fail before long-running work starts.
Sourcefn as_reloadable(&self) -> Option<&dyn Reloadable<S>>
fn as_reloadable(&self) -> Option<&dyn Reloadable<S>>
Optional capability hook.
Sourcefn as_finalizable(&self) -> Option<&dyn Finalizable<S>>
fn as_finalizable(&self) -> Option<&dyn Finalizable<S>>
Optional capability hook.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".