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
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
/*!
<a href="https://github.com/Nercury/di-rs">
<img style="position: absolute; top: 0; left: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_left_green_007200.png" alt="Fork me on GitHub">
</a>
<style>.sidebar { margin-top: 53px }</style>
*/
/*!
# Dependency Injection container for Rust
This page contains an abstract (although with few examples) overview
of this dependency injection container. To see more fine-grained
usage examples without much prose, browse the [Registry](registry/struct.Registry.html)
documentation.
# Overview
## Declarative feature configuration
If a project needs X, Y and Z features, it should be
possible to declare a list of these features, and the features should
discover each other and configure themselves.
## Dependency injection (DI) as a method
First of all, while __dependency injection__ term is just a fancy name for
"passing arguments to constructor", it also communicates a specific
intent: __the user of these arguments does not care about
the way they were created__.
The simpliest form of dependency abstraction is use of trait objects
as construction arguments.
As an example, a `Logger` trait can be passed as an argument to something
that needs a `Logger`. Our library would depend on the abstraction,
and it would not need to care how this actual `Logger` logs the messages.
Without using this "di" library:
```rust
trait Logger { fn log(&self, m: &str); }
struct ConsoleLogger;
impl Logger for ConsoleLogger {
fn log(&self, m: &str) {
println!("{}", m);
}
}
struct OurLibrary;
impl OurLibrary {
fn new(logger: Box<Logger>) -> OurLibrary {
logger.log("Library created!");
OurLibrary
}
}
OurLibrary::new(box ConsoleLogger); // will print "Library created!"
```
Pluging it into "di" looks like this:
```rust
# trait Logger { fn log(&self, m: &str); }
# struct ConsoleLogger;
# impl Logger for ConsoleLogger {
# fn log(&self, m: &str) {
# println!("{}", m);
# }
# }
# struct OurLibrary;
# impl OurLibrary {
# fn new(logger: Box<Logger>) -> OurLibrary {
# logger.log("Library created!");
# OurLibrary
# }
# }
let mut registry = di::Registry::new();
registry.insert_one("logger", || box ConsoleLogger as Box<Logger>);
registry
.one("our_library", |logger: Box<Logger + 'static>| {
logger.log("Hello world!");
OurLibrary
})
.with_arg("logger")
.insert();
// The compilation phase is going to validate the registry
// definitions when the application is started.
match registry.compile() {
Ok(container) => {
container.get::<OurLibrary>("our_library").unwrap()
.take(); // will print "Library created!"
},
Err(errors) => di::error_printer::pretty_print(&errors),
}
```
## Aggregate construction
Sometimes some dependency might be optional. Or maybe there
are multiple valid dependencies for the same interface.
Let's say there can be multiple `Backend` implementations for `Logger`.
It would be great if our `Logger` could depend on any amount of `Backend`
abstractions (without actually depending on implementations)
and get them collected into one list which could be injected as
an argument. This library can do that.
In this example, the concrete implementation would be `RedisBackend`, which
would also require some kind of `Redis` as another dependency.
```rust
use di::Registry;
# struct Redis;
# impl Redis { fn new() -> Redis { Redis } }
# struct RedisBackend { runner: Redis };
# impl RedisBackend {
# fn new(runner: Redis) -> RedisBackend {
# RedisBackend { runner: runner }
# }
# }
# impl Backend for RedisBackend {}
# trait Backend {}
# struct Logger { backends: Vec<Box<Backend + 'static>> }
# impl Logger {
# fn new(backends: Vec<Box<Backend + 'static>>) -> Logger {
# Logger {
# backends: backends
# }
# }
# fn log(&self, _m: &str) {}
# }
fn enable_redis_logs(registry: &mut Registry) {
registry
.one_of("backends", |redis| {
box RedisBackend::new(redis) as Box<Backend>
})
.add_arg("redis") // It will need "redis"
.insert();
registry
.one("redis", || Redis::new())
.insert();
}
fn enable_logger(registry: &mut Registry) {
registry
.one("logger", |backends| {
Logger::new(backends)
})
.add_arg("backends")
.insert();
registry.may_be_empty::<Box<Backend>>("backends");
}
let mut registry = Registry::new();
// List of features in our application.
enable_redis_logs(&mut registry);
enable_logger(&mut registry);
match registry.compile() {
Ok(container) => {
// Get a factory that constructs the logger object.
let logger_factory = container.get::<Logger>("logger").unwrap();
// Actually invoke construction.
let logger = logger_factory.take();
logger.log("Loaded!");
},
Err(errors) => {
di::error_printer::pretty_print(&errors);
panic!("expected no errors");
}
}
```
Obviously, the above is equivalent to this simple code:
```rust
# struct Redis;
# impl Redis { fn new() -> Redis { Redis } }
# struct RedisBackend { runner: Redis };
# impl RedisBackend {
# fn new(runner: Redis) -> RedisBackend {
# RedisBackend { runner: runner }
# }
# }
# impl Backend for RedisBackend {}
# trait Backend {}
# struct Logger { backends: Vec<Box<Backend + 'static>> }
# impl Logger {
# fn new(backends: Vec<Box<Backend + 'static>>) -> Logger {
# Logger {
# backends: backends
# }
# }
# fn log(&self, _m: &str) {}
# }
fn get_logger_factory() -> Logger {
// Regis logs feature
let redis = Redis::new();
let redis_backend = box RedisBackend::new(redis) as Box<Backend>;
// Logger feature
let logger = Logger::new(vec![redis_backend]);
logger
}
// Use logger:
let logger = get_logger_factory();
logger.log("hello");
```
In fact, `registry.compile` constructs internal execution structure
that is very similar to `get_logger_factory`, but it is done at
runtime.
If the "redis logs" is not enabled, the `enable_logger` code does not
need re-compilation, and constructed `get_logger_factory` won't
have any code related to redis:
```ignore
fn get_logger_factory() -> Logger {
// Logger feature
let logger = Logger::new(Vec::new());
logger
}
```
## The roles of `Registry` and `Container`
`Registry` is mutable, `Container` is immutable.
`Registry` is changed on initialization, then it validates all definitions and
"compiles" the `Container`, which is used at runtime.
Further changes to `Registry` can be used to produce a new, different
`Container`.
HashMap and Any are used only on `Registry` configuration phase.
If there are any mistakes, the validator should produce error messages
that pinpoint problems accurately.
The compiled container contains initialization trees constructed from
registry definitions. If the `Container` construction succeeds, factories
returned by container should never fail. If they do, it is a bug.
# Discussion
This is opionated part.
## Why did this library happen
I wanted to implement DI mechanism for Rust because I had great success
with it before. The idea of "container" and "registering" definitions in
it [comes from Symfony2][symfony2-container-component] framework.
However, I intentionaly chose to avoid implementing things the same way
Symfony2 does.
For example, the factories do not return singletons by default. However,
they can be easily added using clonable value over `Rc` wrapper.
The initialization mechanism requires a closure or clonable value.
If you do not like that, well, it is possible to implement
`metafactory::ToMetafactory` trait for anything you would like to use
for value construction.
It was possible to use container itself as an argument for service construction.
It was highly discouraged anti-pattern. So, no such thing here.
The container was highly coupled to the way configuration is loaded. I
consider configuration not a concern of this library, therefore nothing
like that will be implemented.
There was a way to register "passes" for compilation and let various bundles
to modify the registry before the container is actually built. I might
consider adding this.
There is no "priority" yet for aggregates. I am a bit weary of this "feature"
based on the hours spent hunting down services that load in wrong order.
But I am considering that ability to explicitly "override" some definition
might be ok.
There is one other pattern that is a bit burried in Symfony2 di: it is the
ability to "tag" services and then have the container inject all of them
based on that "tag". [Using that is not straightforward][symfony2-tagged-services].
However, I found that it was the key for decoupling features properly, so I chose
to make it __very__ easy to use the equivalent functionality in this library.
That's where `one_of` method with factory `Aggregate` came from.
[symfony2-container-component]: http://symfony.com/doc/current/components/dependency_injection/introduction.html
[symfony2-tagged-services]: http://symfony.com/doc/current/components/dependency_injection/tags.html
## Higher-level features and dependencies
As I briefly mentioned before, my motivation for creating this library
is managing the feature decoupling. Now I will talk
what I mean by "feature".
The worst case scenario in huge application happens when the low level tools
that need to be reliable become coupled to some idiosyncrasies of the
project.
What user (project) wants (requires) does not usually live well with the idea
of having a stable code. Likewise, replacing crucial library does not live
well with the stability the user (project) expects (needs).
Ultimately, these things should live in separate libraries. To distinguish
these libraries, I call code that is responsible for project a "feature", and
the low-level tools simply "libraries".
This `di` library is intended to be one of the ways to manage separation
of features from libraries. The `di` should live at the project-level.
It should register the available libraries to do what needs to be done,
and additionaly provide the "extension" points.
Then, the functionality that is specific only to current project should
be implemented as extension (over `one_of`) or replacement (over `override`
(which is not done yet :P)).
## Usability in actual plugins
This needs to be investigated. Might be fun.
## The true cost of a factory
`Container` will return copies of constructed factories,
which are nothing more than object-initialization trees that can be invoked.
While it was certainly possible to eliminate a lot of indirection in this
tree using unsafe hacks, this library currently uses __no__ unsafe code.
This is a approximate example of calls the
`Logger` construction with `redis_logs` enabled would make:
- `Factory<Logger>.take()` - wraps `Logger` getter mechanism in struct
- `Closure_ManyArg_1<Vec<Box<Backend>>, Logger>.take()` - closure dependency scope
- `AggregateGetter<Vec<Box<Backend>>>.take()` - aggregates ("one_of") dependencies
- `Factory<Box<Backend>>.take()` - wraps getter mechanism in struct
- `Closure_ManyArg_1<Redis, Box<Backend>>.take()` - closure dependency scope
- `Factory<Redis>.take()`
- `Closure_ZeroArg<Redis>.take()` - call Redis construction closure
- `Rc<RefCell<|| -> Redis>>.call()`
- `Rc<RefCell<|Redis| -> Box<Backend>>>.call()` - call `RedisBackend` construction closure
- `Rc<RefCell<|Vec<Box<Redis>>| -> Logger>>.call()` - call `Logger` construction closure
The constly point here is probably a `Rc<RefCell>` and allocation of
`Vec<Box<Backends>>`, though this is just a guess at this point.
## Further ideas
Currently registry definitions are initialized from clonable values or closures.
It is possible to extend it with unboxed closures and channels by implementing
`ToMetafactory` trait for them in `metafactory` library.
It might be possible to compile the `Container` at actual compilation instead of
runtime. That would probably require a compiler plugin.
*/
extern crate term;
extern crate typedef;
extern crate metafactory;
pub use Registry;
pub use Container;