1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
//! # Runtime dependency injection. //! //! By default, services provided by the [`Injector`] use thread-safe pointers. //! This is because [`Arc<T>`](std::sync::Arc) is used to hold instances of the //! services. This can be changed to [`Rc<T>`](std::rc::Rc) by disabling //! default features and enabling the "rc" feature: //! //! ```text //! [dependencies.runtime_injector] //! version = "*" # Replace with the version you want to use //! default-features = false //! features = ["rc"] //! ``` //! //! ## Getting started //! //! If you are unfamiliar with dependency injection, then you may want to check //! about how a container can help //! [simplify your application][ioc]. Otherwise, //! check out the [getting started guide][getting-started] //! //! [ioc]: crate::docs::inversion_of_control //! [getting-started]: crate::docs::getting_started //! //! ## Dependency injection at runtime (rather than compile-time) //! //! Runtime dependency injection allows for advanced configuration of services //! during runtime rather than needing to decide what services your application //! will use at compile time. This means you can read a config when your //! application starts, decide what implementations you want to use for your //! interfaces, and assign those at runtime. This is also slightly slower than //! compile-time dependency injection, so if pointer indirection, dynamic //! dispatch, or heap allocations are a concern, then a compile-time dependency //! injection library might work better instead. However, in asynchronous, //! I/O-based applications like a web server, the additional overhead is //! probably insignificant compared to the additional flexibility you get with //! runtime_injector. //! //! ## Interfaces //! //! Using interfaces allows you to write your services without worrying about //! how its dependencies are implemented. You can think of them like generic //! type parameters for your service, except rather than needing to add a new //! type parameter, you use a service pointer to the interface for your //! dependency. This makes your code easier to read and faster to write, and //! keeps your services decoupled from their dependencies and dependents. //! //! Interfaces are implemented as trait objects in runtime_injector. For //! instance, you may define a trait `UserDatabase` and implement it for //! several different types. [`Svc<dyn UserDatabase>`](crate::Svc<T>) is a //! reference-counted service pointer to an implementation of your trait. //! Similarly, `dyn UserDatabase` is your interface. You can read more about //! how interfaces work and how they're created in the //! [type-level docs](crate::Interface). //! //! ## Service lifetimes //! //! Lifetimes of services created by the [`Injector`] are controlled by the //! [`Provider`] used to construct those lifetimes. Currently, there are three //! built-in service provider types: //! //! - **[Transient](crate::TransientProvider):** A service is created each time //! it is requested. This will never return the same instance of a service //! more than once. //! - **[Singleton](crate::SingletonProvider):** A service is created only the //! first time it is requested, then that single instance is reused for each //! future request. //! - **[Constant](crate::ConstantProvider):** Used for services that are not //! created using a service factory and instead can have their instance //! provided to the container directly. This behaves similar to singleton in //! that the same instance is provided each time the service is requested. //! //! Custom service providers can also be created by implementing either the //! [`TypedProvider`] or [`Provider`] trait. //! //! ## Fallible service factories //! //! Not all types can always be successfully created. Sometimes, creating an //! instance of a service might fail. Rather than panicking on error, it's //! possible to instead return a [`Result<T, E>`] from your constructors and //! inject the result as a [`Svc<T>`]. Read more in the //! [docs for `IntoFallible`](crate::IntoFallible). //! //! ## Owned service pointers //! //! In general, providers need to be able to provide their services via //! reference-counted service pointers, or [`Svc<T>`]. The issue with this is //! that you cannot get mutable or owned access to the contents of those //! pointers since they are shared pointers. As a result, you may need to clone //! some dependencies in your constructors if you want to be able to own them. //! //! If your dependency is a transient service, then it might make more sense //! to inject it as a [`Box<T>`] than clone it from a reference-counted service //! pointer. In these cases, you can request a [`Box<T>`] directly from the //! injector and avoid needing to clone your dependency entirely! //! //! ## Custom target-specific arguments //! //! Sometimes it's useful to be able to pass a specific value into your //! services. For example, if you're writing a database service and you need a //! connection string, you could define a new `ConnectionString` struct as a //! newtype for [`String`], but that would be a bit excessive for passing in a //! single value. If you had several arguments you needed to pass in this way, //! then that would mean you would need a new type for each one. //! //! Rather than creating a bunch of newtypes, you can use [`Arg<T>`] to pass in //! pre-defined values directly to your services. For example, you can use //! `Arg<String>` to pass in your connection string, plus you can use //! `Arg<usize>` to set the max size of your connection pool, and another //! `Arg<String>` in your logging service to set your logging format without //! needing to worry about accidentally using your connection string as your //! logging format! //! //! ## Example //! //! ``` //! use runtime_injector::{ //! define_module, Module, interface, Injector, Svc, IntoSingleton, //! TypedProvider, IntoTransient, constant //! }; //! use std::error::Error; //! //! // Some type that represents a user //! struct User; //! //! // This is our interface. In practice, multiple structs can implement this //! // trait, and we don't care what the concrete type is most of the time in //! // our other services as long as it implements this trait. Because of this, //! // we're going to use dynamic dispatch later so that we can determine the //! // concrete type at runtime (vs. generics, which are determined instead at //! // compile time). //! // //! // The `Send` and `Sync` supertrait requirements are only necessary when //! // compiling with the "arc" feature to allow for service pointer //! // downcasting. //! trait DataService: Send + Sync { //! fn get_user(&self, user_id: &str) -> Option<User>; //! } //! //! // We can use a data service which connects to a SQL database. //! #[derive(Default)] //! struct SqlDataService; //! impl DataService for SqlDataService { //! fn get_user(&self, _user_id: &str) -> Option<User> { todo!() } //! } //! //! // ... Or we can mock out the data service entirely! //! #[derive(Default)] //! struct MockDataService; //! impl DataService for MockDataService { //! fn get_user(&self, _user_id: &str) -> Option<User> { Some(User) } //! } //! //! // Specify which types implement the DataService interface. This does not //! // determine the actual implementation used. It only registers the types as //! // possible implementations of the DataService interface. //! interface!(dyn DataService = [SqlDataService, MockDataService]); //! //! // Here's another service our application uses. This service depends on our //! // data service, however it doesn't care how that service is actually //! // implemented as long as it works. Because of that, we're using dynamic //! // dispatch to allow the implementation to be determined at runtime. //! struct UserService { //! data_service: Svc<dyn DataService>, //! } //! //! impl UserService { //! // This is just a normal constructor. The only requirement is that each //! // parameter is a valid injectable dependency. //! pub fn new(data_service: Svc<dyn DataService>) -> Self { //! UserService { data_service } //! } //! //! pub fn get_user(&self, user_id: &str) -> Option<User> { //! // UserService doesn't care how the user is actually retrieved //! self.data_service.get_user(user_id) //! } //! } //! //! fn main() -> Result<(), Box<dyn Error>> { //! // This is where we register our services. Each call to `.provide` adds //! // a new service provider to our container, however nothing is actually //! // created until it is requested. This means we can add providers for //! // types we aren't actually going to use without worrying about //! // constructing instances of those types that we aren't actually using. //! let mut builder = Injector::builder(); //! //! // We can manually add providers to our builder //! builder.provide(UserService::new.singleton()); //! //! struct Foo(Svc<dyn DataService>); //! //! // Alternatively, modules can be used to group providers and //! // configurations together, and can be defined via the //! // define_module! macro //! let module = define_module! { //! services = [ //! // Simple tuple structs can be registered as services directly without //! // defining any additional constructors //! Foo.singleton(), //! //! // Note that we can register closures as providers as well //! (|_: Svc<dyn DataService>| "Hello, world!").singleton(), //! (|_: Option<Svc<i32>>| 120.9).transient(), //! //! // Since we know our dependency is transient, we can request an //! // owned pointer to it rather than a reference-counted pointer //! (|value: Box<f32>| format!("{}", value)).transient(), //! //! // We can also provide constant values directly to our services //! constant(8usize), //! ], //! interfaces = { //! // Let's choose to use the MockDataService as our data service //! dyn DataService = [MockDataService::default.singleton()], //! }, //! }; //! //! // You can easily add a module to your builder //! builder.add_module(module); //! //! // Now that we've registered all our providers and implementations, we //! // can start relying on our container to create our services for us! //! let injector = builder.build(); //! let user_service: Svc<UserService> = injector.get()?; //! let _user = user_service.get_user("john"); //! //! Ok(()) //! } //! ``` #![forbid(unsafe_code)] #![deny(clippy::all, clippy::pedantic)] #![warn(missing_docs)] #![allow( clippy::module_name_repetitions, clippy::missing_errors_doc, clippy::doc_markdown, clippy::needless_doctest_main, clippy::needless_pass_by_value )] #[cfg(not(any(feature = "arc", feature = "rc")))] compile_error!( "Either the 'arc' or 'rc' feature must be enabled (but not both)." ); #[cfg(all(feature = "arc", feature = "rc"))] compile_error!( "The 'arc' and 'rc' features are mutually exclusive and cannot be enabled together." ); mod any; mod builder; mod injector; mod iter; mod module; mod requests; mod services; pub use any::*; pub use builder::*; pub use injector::*; pub use iter::*; pub use module::*; pub use requests::*; pub use services::*; pub mod docs; #[cfg(test)] mod tests;