Contributions Prototype
Some things are built from many modules: a command-line tree, a set of routes, a list of health checks. Each module contributes a piece; a target assembles them.
Capability or contribution?
| Capability | Contribution | |
|---|---|---|
| Used | While the application runs | While a subsystem is being built |
| How many | Exactly one resolved provider | Any number, collected by a target |
| Example | Clock | A CLI command |
The target owns the rules
Core and Kernel know only generic shapes: a contribution, a target, and TargetComposition<T, Q> that keeps target and qualifier types intact. Everything that is specific to commands lives in the CLI target:
| Generic, in Core/Kernel | Command-specific, in the CLI target |
|---|---|
| Stable contribution and target identities | Command names are unique |
Contribution and ContributionTarget contracts | Commands are ordered alphabetically |
| Orphan diagnostics with typed target context | An empty command tree is valid |
| Target errors returned without interpretation | Lookup and unknown-command behaviour |
So a third-party target can be written against the public extension traits alone. The example's tests include one.
Assembling a command tree
let target = CliCommandTarget::<PublicCommands>::new();
let commands = TargetComposition::<_, PublicCommands>::new(vec![
(HelloModule::ID, HelloModule::contribution()),
(GoodbyeModule::ID, GoodbyeModule::contribution()),
])
.build(Some(&target))
.expect("the CLI target accepts these declarations")
.expect("a target produces a command tree");
println!("hello: {}", commands.execute("hello", Some(&clock)).unwrap());
println!("goodbye: {}", commands.execute("goodbye", None).unwrap());
hello reads the Clock capability; goodbye does not need one. Public and admin commands are different Rust types, and a compile-fail doc test proves a public command cannot be given to the admin target.
Orphans
A contribution marked required with no matching target is a structured error, not a silent no-op. A dormant orphan in a process you are not running does not fail the one you are (see Processes).
Cost
Assembling two declarations took a median 86 ns; the finished tree held 96 bytes for two entries and keeps only names and function pointers, not the composition wrappers. Measured on one machine — see Evidence.