Lazy Initialization

These features are only available if the lazy feature is activated

There are some scenarios where you know or have high reason to believe that a particular service composition will be expensive to create. The requirement to eagerly load every injected service instance in such situations is undesirable. There are several methods by which you can differ dependency resolution, including declaring a parameter which would inject the ServiceProvider itself. Using the Service Locator pattern in this manner hides dependencies and is considered to be an anti-pattern. The lazy feature provides an out-of-the-box facility to address this problem.

The Lazy struct is a holder that resolves a service in a lazily evaluated manner. The Lazy struct itself is owned by the struct it is injected into and the lifetime of the service resolved is unchanged. The key difference is that the injected service dependency is well-known at the call site, but its evaluation is differed.

Consider the following:

use di::*;

#[derive(Default)]
pub struct Expensive {
    // expensive stuff here
}

impl Expensive {
    pub fn do_work(&self) {
        // use expensive stuff
    }
}

pub struct Needy {
    expensive: Lazy<Ref<Expensive>>
}

impl Needy {
    pub fn new(expensive: Lazy<Ref<Expensive>>) -> Self {
        Self { expensive }
    }

    pub fn run(&self) {
        self.expensive.value().do_work()
    }
}

The Needy struct defines a Lazy that wraps around a service dependency. This allows the service to be evaluated on-demand and also keeps the Expensive struct visible as a required collaborator.

Despite being a generic type, Lazy can only be created using the utility functions from the lazy module as follows:

FunctionResolution
lazy::exactly_oneA required service lazily
lazy::exactly_one_mutA required, mutable service lazily
lazy::exactly_one_with_keyA required service with a key lazily
lazy::exactly_one_with_key_mutA required, mutable service with key lazily
lazy::zero_or_oneAn optional service lazily
lazy::zero_or_one_mutAn optional, mutable service lazily
lazy::zero_or_one_by_keyAn optional, service with a key lazily
lazy::zero_or_one_by_key_mutAn optional, mutable service with a key lazily
lazy::zero_or_moreOne or more services lazily
lazy::zero_or_more_mutOne or more mutable services lazily
lazy::zero_or_more_by_keyOne or more services with a key lazily
lazy::zero_or_more_by_key_mutOne or more mutable services with a key lazily
lazy::missingAlways resolves None
lazy::missing_with_keyAlways resolves None
lazy::emptyAlways resolves Vec::with_capacity(0)
lazy::empty_with_keyAlways resolves Vec::with_capacity(0)
lazy::initInitializes from an instance (ex: testing)
lazy::init_mutInitializes from a mutable instance (ex: testing)
lazy::init_by_keyInitializes from a keyed instance (ex: testing)
lazy::init_by_key_mutInitializes from a mutable, keyed instance (ex: testing)

Lazy is a special type which cannot be resolved directly from a ServiceProvider. You will need construct one or more Lazy registrations in the activation factory method. For example:

use crate::*;
use di::*;

fn main() {
    let provider = ServiceCollection::new()
        .add(singleton_as_self::<Expensive>()
             .from(|_| Rc::new(Expensive::default())));
        .add(singleton_as_self::<Needy>()
             .depends_on(exactly_one::<Expensive>())
             .from(|sp| Rc::new(Needy::new(lazy::exactly_one(sp.clone())))))
        .build_provider()
        .unwrap();
    let needy = provider.get_required::<Needy>();
    needy.run()
}

Note: singleton_as_self and exactly_one are functions provided by the builder feature, while lazy::exactly_one is provided by the lazy feature.

When #[injectable] is used, it will generate the appropriate lazy function for the injected call site.