CGP v0.6.0 Release - Major ergonomic improvements for provider and context implementations
Posted on 2025-10-26
Authored by Soares Chen

Overview

It has only been two weeks since v0.5.0 was released, yet we are already introducing another major update. CGP v0.6.0 brings significant improvements to the ergonomics of implementing providers and contexts, making it easier and more intuitive for developers to build on top of CGP.

Simplified provider implementation with #[cgp_impl]

The highlight of v0.6.0 is the introduction of the new #[cgp_impl] macro, which replaces #[cgp_provider] and greatly simplifies the way provider traits are implemented in CGP.

Essentially, #[cgp_impl] lets you write a provider trait implementation as if it were a blanket implementation for the consumer trait. This makes implementing CGP providers feel as natural as working with regular Rust traits, reducing boilerplate and making the intent clearer.

Example

Consider the following example trait:

#[cgp_component(HttpRequestFetcher)]
pub trait CanFetchHttpRequest: HasErrorType {
    fn fetch_http_request(&self, request: Request) -> Result<Response, Self::Error>;
}

With #[cgp_impl], you can now implement a provider like this:

#[cgp_impl(new FetchWithHttpClient)]
impl<Context> HttpRequestFetcher for Context
where
    Self: HasHttpClient + HasErrorType,
{
    fn fetch_http_request(&self, request: Request) -> Result<Response, Self::Error> {
        ...
    }
}

Previously, the same functionality required using #[cgp_provider], which looked like this:

#[cgp_new_provider]
impl<Context> HttpRequestFetcher<Context> for FetchWithHttpClient
where
    Context: HasHttpClient + HasErrorType,
{
    fn fetch_http_request(context: &Context, request: Request) -> Result<Response, Context::Error> {
        ...
    }
}

As shown above, #[cgp_impl] produces syntax that is much closer to standard Rust trait implementations, making provider definitions easier to read and write.

Behind the scenes, #[cgp_impl] expands to the same form as a manually written provider implementation using #[cgp_provider]. Understanding how provider traits work remains important, especially when debugging or exploring the generated code.

Direct component delegation on context types

In v0.6.0, we can now use delegate_components! directly on the context type itself, without requiring a separate provider struct. This makes it possible to write code like the following:

pub struct App { ... }

delegate_components! {
    App {
        FooComponent: FooProvider,
        BarComponent: BarProvider,
        ...
    }
}

Previously, it was necessary to use a separate provider for the context:

#[cgp_context]
pub struct App { ... }

delegate_components! {
    AppComponents {
        FooComponent: FooProvider,
        BarComponent: BarProvider,
        ...
    }
}

This new approach significantly simplifies how components are wired to a concrete context. There is no longer a need to introduce an extra AppComponents struct just to serve as a type-level lookup table. The lookup table is now embedded directly in the App type itself.

This change can also yield a small improvement in compile times, since there is one fewer level of indirection for the trait solver to handle when resolving provider relationships.

Direct implementation of consumer traits

A major benefit of this improvement is the ability to implement consumer traits directly on a concrete context type. For example:

#[cgp_getter]
pub trait HasName {
    fn name(&self) -> &str;
}

#[cgp_getter]
pub trait HasCount {
    fn count(&self) -> u32;
}

#[derive(HasField)]
pub struct App {
    pub name: String,
    pub count: u32,
}

delegate_components! {
    App {
        NameGetterComponent: UseField<Symbol!("name")>,
    }
}

// Consumer trait can now be implemented directly
impl HasCount for App {
    fn count(&self) -> u32 {
        self.count
    }
}

In earlier versions, you would have needed to write the following instead:

#[cgp_provider]
impl CountGetter<App> for AppComponents {
    fn count(app: &App) -> u32 {
        app.count
    }
}

This older style often made code appear verbose and confusing, particularly for newcomers. The new approach is cleaner and more intuitive, aligning better with standard Rust trait conventions.

Unlock use of #[cgp_component] on any trait

This simplification also enables #[cgp_component] to be applied to nearly any existing Rust trait without breaking existing code. This is a major step toward making CGP easier to adopt, since developers can start integrating it gradually without needing to first learn all its concepts.

For example, in principle it is now possible to annotate the standard library’s Hash trait with #[cgp_component]:

#[cgp_component(HashProvider)]
pub trait Hash { ... }

This does not affect existing code that uses or implements Hash, but it allows new overlapping implementations such as:

#[cgp_impl(HashWithDisplay)]
impl<T: Display> HashProvider for T {
    ...
}

You can then reuse this implementation on any type using delegate_components!:

pub struct MyData { ... }

impl Display for MyData { ... }

delegate_components! {
    MyData {
        HashProviderComponent: HashWithDisplay,
    }
}

With this capability, CGP can now enhance any existing Rust trait without changing how those traits are implemented. By supporting overlapping and orphan implementations safely, CGP v0.6.0 makes it far easier and more appealing for developers to experiment with the framework, since the onboarding cost is now significantly lower.

Removal of HasCgpProvider trait

Consumer traits can now be implemented directly because CGP has removed the HasCgpProvider trait. Instead of relying on HasCgpProvider, blanket implementations for consumer traits now use DelegateComponent, just as provider traits do.

For example, the HasName trait introduced earlier now expands into the following blanket implementation:

impl<Context> HasName for Context
where
    Context: DelegateComponent<NameGetterComponent>,
    Context::Delegate: NameGetter<Context>,
{
    fn name(&self) -> &str {
        Context::Delegate::name(self)
    }
}

Previously, the generated code looked like this:

impl<Context> HasName for Context
where
    Context: HasCgpProvider,
    Context::CgpProvider: NameGetter<Context>,
{
    fn name(&self) -> &str {
        Context::CgpProvider::name(self)
    }
}

The old design had an important limitation: any type implementing HasCgpProvider could not also implement the corresponding consumer trait directly, because the blanket implementation would already cover it. With the new approach, this restriction no longer applies. A type that implements DelegateComponent can still define its own consumer trait implementation, as long as there is no conflicting implementation for the same component key. This means developers can freely implement consumer traits on their context types without running into conflicts, as long as the delegation remains unambiguous.

Backward Compatibility

Since many projects already use CGP, removing context providers entirely could cause extensive breakage. To avoid this, the new version modifies the behavior of #[cgp_context] so that it automatically provides blanket DelegateComponent<Name> implementations for all component names, preserving compatibility with existing code.

For instance, consider the following context definition:

#[cgp_context]
pub struct App { ... }

The macro now generates the following code:

pub struct AppComponents;

impl<Name> DelegateComponent<Name> for App {
    type Delegate = AppComponents;
}

This bulk delegation plays the same role that HasCgpProvider once did, ensuring that older codebases continue to function correctly. Previously, the generated code would have looked like this:

pub struct AppComponents;

impl HasCgpProvider for App {
    type CgpProvider = AppComponents;
}

By automatically generating the bulk delegation, CGP v0.6.0 maintains backward compatibility while adopting a cleaner and more flexible design.

Background

To understand why HasCgpProvider was used in the first place, it helps to look back at CGP’s early design. The original idea was to allow multiple concrete contexts to share a single provider “table.” For example:

pub struct AppComponents;

pub struct AppA { ... }
pub struct AppB { ... }

impl HasCgpProvider for AppA {
    type CgpProvider = AppComponents;
}

impl HasCgpProvider for AppB {
    type CgpProvider = AppComponents;
}

In this design, different applications could reuse the same wiring setup without reconfiguring components each time. However, in practice, many contexts shared most of their wiring but required small customizations. This need for partial reuse led to the introduction of the preset feature, which provided the same flexibility without the drawbacks of shared context providers.

Over time, the HasCgpProvider system became a remnant of CGP’s early architecture. It persisted mainly out of concern for backward compatibility. After reevaluating the issue, it became clear that generating DelegateComponent implementations through #[cgp_context] could preserve compatibility while removing unnecessary complexity. This realization made it possible to remove HasCgpProvider entirely in v0.6.0, simplifying the design and improving flexibility without disrupting existing users.

Introduce #[cgp_inherit] macro

With the deprecation of #[cgp_context], CGP v0.6.0 introduces a new and clearer way for concrete contexts to inherit from a preset. The new #[cgp_inherit] macro provides this functionality directly, allowing a context to build upon a preset without the need for an additional provider type.

Example

Given the following:

#[cgp_inherit(MyPreset)]
pub struct App { ... }

is roughly equivalent to the previous way of expressing preset inheritance with #[cgp_context]:

#[cgp_context(AppComponents: MyPreset)]
pub struct App { ... }

The key difference is that with #[cgp_inherit], the preset inheritance happens directly on the App context itself. There is no need to generate an intermediary AppComponents provider type that inherits from MyPreset. This makes the inheritance mechanism simpler and more transparent.

Behind the scenes, #[cgp_inherit] generates code similar to the following:

impl<Name> DelegateComponent<Name> for App
where
    Self: MyPreset::IsPreset<Name>,
    MyPreset::Components: DelegateComponent<Name>,
{
    type Delegate = <MyPreset::Components as DelegateComponent<Name>>::Delegate;
}

This generated implementation delegates component resolution to the preset whenever the component key is part of that preset. At the same time, the design allows for flexibility: even with these blanket implementations, you can still implement consumer traits directly on the App context for any component keys that are not provided by the preset.

Migration Guide

In most cases, upgrading to v0.6.0 should not require any changes to existing code. The update has been designed with backward compatibility in mind, allowing projects to transition smoothly while taking advantage of the new ergonomic improvements.

Removal of HasCgpProvider trait

The most significant breaking change in v0.6.0 is the removal of the HasCgpProvider trait and the change in how consumer trait blanket implementations are generated. However, most CGP users do not directly interact with these internal constructs, so existing code should continue to compile without modification. The change primarily affects macro-generated code and internal delegation logic rather than user-defined traits or providers.

Deprecation of #[cgp_context]

The #[cgp_context] macro has been retained for backward compatibility, ensuring that existing projects using context providers will continue to function as before. However, it is strongly recommended to remove #[cgp_context] when upgrading to v0.6.0. Doing so allows your context types to directly implement consumer traits, taking full advantage of the simplified delegation system and cleaner trait relationships introduced in this release.

Deprecation of #[cgp_provider]

The introduction of the #[cgp_impl] macro replaces the need for #[cgp_provider] when defining provider implementations. Existing uses of #[cgp_provider] will still compile and function correctly in v0.6.0, but developers are encouraged to migrate to #[cgp_impl] for new code. The new syntax is closer to standard Rust trait implementations, making provider definitions easier to read and reason about.

It is also advisable to migrate all existing provider implementations to use #[cgp_impl], especially in projects with multiple contributors. Mixing both macros can lead to confusion among developers unfamiliar with CGP, as they may wonder why two different styles exist. In contrast, #[cgp_impl] is intuitive to Rust users, since it effectively represents a named blanket implementation and requires no prior knowledge of CGP’s internal model.


Updates

RustLab Presentation

RustLab 2025

The time is drawing near, and I will be presenting How to Stop Fighting with Coherence and Start Writing Context-Generic Trait Impls at RustLab on November 3rd. I look forward to meeting everyone attending the conference!

Acknowledgement

Thank you April Gonçalves and Dzmitry Lahoda for sponsoring the development of CGP!