Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 (GreetHello via impl Greeter)
  • context = the concrete type that picks providers (Person)
  • component key = the selector used in wiring (GreeterComponent)

How the relationship works

  1. #[cgp_component(Greeter)] defines a CGP capability from CanGreet.
  2. #[cgp_impl(new GreetHello)] impl Greeter says GreetHello implements that capability’s provider trait.
  3. delegate_components! on Person selects GreetHello for the GreeterComponent slot.
  4. 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:

  1. Define what callers can ask for (CanGreet).
  2. Define how that behavior is implemented (GreetHello).
  3. Define who receives the behavior (Person).
  4. 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:

  • CanGreet is the consumer trait you wrote.
  • Greeter is the provider trait name you passed to #[cgp_component(Greeter)].
  • GreeterComponent is the generated component key type used by delegate_components!.
  • GreetHello is the provider type name you chose in #[cgp_impl(new GreetHello)].

Rules to remember:

  • impl Greeter must match the provider-trait name from #[cgp_component(Greeter)].
  • fn greet in the provider impl must match fn greet in CanGreet.
  • GreeterComponent is used in wiring because it is the component key type.
  • Names like Person, HasName, and GreetHello are 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.