Context, Component, Provider
This chapter shows the core CGP relationship in code:
- a context is your concrete app type,
- a component is a capability slot,
- a provider is the implementation plugged into that slot.
If you remember one sentence, use this: the context chooses a provider for each component.
You can say the same idea in a few equivalent ways:
- A context gets a component’s behavior by wiring that component to a provider.
- A context implements a component’s consumer trait by delegating to a provider.
- A component defines the capability, a provider defines the implementation, and the context chooses the provider.
- A context has a capability when its component key is mapped to a valid provider.
- In CGP, behavior comes from provider selection per component.
Complete example
use cgp::prelude::*;
// 1) Define a capability (consumer trait)
#[cgp_component(Greeter)]
pub trait CanGreet {
fn greet(&self);
}
// 2) Define a dependency used by the provider
#[cgp_auto_getter]
pub trait HasName {
fn name(&self) -> &str;
}
// 3) Define a provider for that capability
#[cgp_impl(new GreetHello)]
impl Greeter
where
Self: HasName,
{
fn greet(&self) {
println!("Hello, {}!", self.name());
}
}
// 4) Define the concrete context
#[derive(HasField)]
pub struct Person {
pub name: String,
}
// 5) Wire context -> component -> provider
delegate_components! {
Person {
GreeterComponent: GreetHello,
}
}
fn main() {
let person = Person {
name: "Ada".into(),
};
person.greet();
}
What each term means in this code
- Context:
Person. - Component:
GreeterComponent(the key used in wiring). - Provider:
GreetHello(the implementation wired for that component).
Person gets CanGreet by wiring GreeterComponent to GreetHello.
Vocabulary: capability, consumer, dependency
- Capability: what a context can do at call-site, like
person.greet(). - Consumer trait: the trait that exposes that capability to callers, like
CanGreet. - Component: the full CGP unit around that capability (consumer trait + provider trait + component key). In wiring code, you usually see the component key type (
GreeterComponent). - Capability slot: another way to describe the component key in a context’s wiring table.
- Provider: the implementation plugged into that slot, like
GreetHello. - Dependency: another trait requirement needed by a provider implementation, like
Self: HasName.
When step (1) says “define a capability,” it means: define the consumer trait (CanGreet) that represents that capability.
When step (2) says “define a dependency,” it means: define required supporting traits/data the provider needs (HasName, and later Person { name: … }).
In short:
- consumer = the interface callers use (
CanGreet) - provider = the implementation of that interface logic (
GreetHelloviaimpl Greeter) - context = the concrete type that picks providers (
Person) - component key = the selector used in wiring (
GreeterComponent)
How the relationship works
#[cgp_component(Greeter)]defines a CGP capability fromCanGreet.#[cgp_impl(new GreetHello)] impl GreetersaysGreetHelloimplements that capability’s provider trait.delegate_components!onPersonselectsGreetHellofor theGreeterComponentslot.- Calling
person.greet()uses the provider chosen by that wiring.
That is the fundamental context-component-provider loop.
Another way to read the same flow:
- Define what callers can ask for (
CanGreet). - Define how that behavior is implemented (
GreetHello). - Define who receives the behavior (
Person). - Wire which implementation that context should use (
GreeterComponent: GreetHello).
Identifier guide (to avoid confusion)
Some names are generated by macros, so here is the direct map:
CanGreetis the consumer trait you wrote.Greeteris the provider trait name you passed to#[cgp_component(Greeter)].GreeterComponentis the generated component key type used bydelegate_components!.GreetHellois the provider type name you chose in#[cgp_impl(new GreetHello)].
Rules to remember:
impl Greetermust match the provider-trait name from#[cgp_component(Greeter)].fn greetin the provider impl must matchfn greetinCanGreet.GreeterComponentis used in wiring because it is the component key type.- Names like
Person,HasName, andGreetHelloare arbitrary; consistency matters, not specific prefixes.
Optional compile-time check
check_components! {
#[check_providers(GreetHello)]
CanUsePerson for Person {
GreeterComponent,
}
}
This confirms the wiring for Person and also confirms GreetHello is valid for that component.