CGP Updates: v0.3.0 Release and New Chapters
Posted on 2025-01-09

Summary

We’re excited to announce the release of v0.3.0 of the cgp crate, along with several new chapters of the CGP Patterns book! This post highlights some of the key features and updates included in this release.

New Book Chapters

We’ve added a few new chapters to the CGP Patterns book. Below is a brief summary of the newly published content.

Associated Types

In the launch announcement for CGP, some readers remarked that CGP seemed to be primarily a dependency injection framework in Rust. While this observation captured part of its functionality, a key feature not yet covered was CGP's ability to use abstract types in conjunction with the dependency injection of types.

In the new chapter of the book, we explore this powerful feature with a motivating example: implementing a context-generic authentication token validator that is not only generic over the Context but also over abstract Time and AuthToken types. The chapter also demonstrates how to use the cgp_type! macro to streamline the declaration of abstract type traits and how to employ UseType in component wiring to instantiate abstract types.

Error Handling

CGP introduces a novel approach to error handling that differs significantly from the conventional patterns used in Rust today. In the new chapter of the book, we begin by leveraging an abstract Error type from HasErrorType to define error-returning method signatures. The chapter then delves into how CanRaiseError and CanWrapError can be used to produce abstract errors within context-generic provider implementations. Further, the chapter discusses how to define context-generic error raisers and leverage the UseDelegate pattern for static dispatching of error handling to various providers.

Field Accessors

CGP provides a robust mechanism for dependency injection using impl-side dependencies. However, since these dependencies are expressed through traits and constraints, we need to define accessor traits to retrieve field values from a generic context.

In the new chapter, we explore different approaches for defining, using, and implementing accessor traits in CGP. This chapter explains how the #[derive(HasField)] macro operates and dives into the internal workings of HasField and symbol!. It also introduces the #[cgp_auto_getter] and #[cgp_getter] macros, which automatically generate accessor provider implementations that work with HasField.

cgp v0.3.0 Release

The cgp crate has been upgraded from v0.2.0 to v0.3.0, introducing new features that significantly enhance usability and include minor breaking changes. You can view the full release notes here. Additionally, the Hello World example on the project homepage has been updated to showcase a simplified implementation using the latest CGP constructs.

Below is a summary of key updates in this release.

cgp_type! Macro

The new cgp_type! macro streamlines the process of declaring abstract type traits, enabling you to define them in a single line of code. For instance, the HasErrorType trait in cgp is now defined as:

cgp_type!( Error: Debug );

The cgp_type! macro expands this short declaration into the following:

#[cgp_component {
    name: ErrorTypeComponent,
    provider: ProvideErrorType,
}]
pub trait HasErrorType {
    type Error: Debug;
}

impl<Context, Error> ProvideErrorType<Context> for UseType<Error>
where
    Error: Debug,
{
    type Error = Error;
}

For a detailed explanation of cgp_type! and its usage, check out the new Associated Types chapter in the CGP Patterns book.

#[cgp_auto_getter] Macro

The #[cgp_auto_getter] macro simplifies the process of defining accessor traits with blanket implementations based on HasField. When a trait is marked with #[cgp_auto_getter], any context deriving HasField that has the required fields and types will automatically implement the specified trait without additional boilerplate.

Here’s an example illustrating how it works:

use cgp::prelude::*;

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

#[derive(HasField)]
pub struct Person {
    pub name: String,
}

fn main() {
    let person = Person {
        name: "Alice".into(),
    };

    println!("Hello, {}", person.name());
}

In this example, the Person struct derives the HasField trait, which automatically implements the HasName trait without any additional code. This means that an accessor trait like HasName can be defined in separate crates or modules, and it will still be automatically implemented for all structs that derive HasField with the necessary fields.

For more details on #[cgp_auto_getter], refer to the Generic Accessor Providers chapter in the CGP Patterns book.

#[cgp_getter] Macro

The #[cgp_getter] macro, like #[cgp_auto_getter], generates blanket implementations that make use of HasField. However, #[cgp_getter] extends functionality by also creating full CGP constructs for the trait, similar to #[cgp_component]. This requires explicit wiring in the context using delegate_components!. Additionally, #[cgp_getter] derives a blanket implementation for the UseFields provider, so that it can be used inside the component wiring as follows:

use cgp::prelude::*;

#[cgp_getter {
    provider: NameGetter,
}]
pub trait HasName {
    fn name(&self) -> &String;
}

#[derive(HasField)]
pub struct Person {
    pub name: String,
}

pub struct PersonComponents;

impl HasComponents for Person {
    type Components = PersonComponents;
}

delegate_components! {
    PersonComponents {
        NameGetterComponent: UseFields,
    }
}

fn main() {
    let person = Person {
        name: "Alice".into(),
    };

    println!("Hello, {}", person.name());
}

For additional information on #[cgp_getter], refer to the Generic Accessor Providers chapter of the CGP Patterns book.

CanWrapError Trait

The CanWrapError trait has been introduced to streamline the process of wrapping existing errors. Its definition is as follows:

#[cgp_component {
    provider: ErrorWrapper,
}]
pub trait CanWrapError<Detail>: HasErrorType {
    fn wrap_error(error: Self::Error, detail: Detail) -> Self::Error;
}

Previously, error wrapping relied on using CanRaiseError with a tuple, such as CanRaiseError<(Self::Error, Detail)>. However, this approach was cumbersome and less intuitive. The CanWrapError trait addresses this issue by providing a cleaner and more straightforward way to raise wrapped errors.

For more details about the usage of CanWrapError, refer to the Error Wrapping chapter in the CGP Patterns book.

cgp-error-anyhow Crate

We have published a new cgp-error-anyhow crate, which provides context-generic error raisers for anyhow::Error. This addition complements the previously published cgp-error-eyre and cgp-error-std crates, which support CGP error handling with eyre::Error and Box<dyn core::error::Error + Send + Sync + 'static>. Given the popularity of anyhow::Error, this crate extends support for its usage.

Details on using cgp-error-anyhow for error handling can be found in the Error Handling chapter of the CGP Patterns book.

cgp-runtime Crate

The new cgp-runtime crate introduces standardized interfaces for runtimes, paving the way for future discussions on asynchronous programming in the CGP Patterns book.

The constructs provided by cgp-runtime, which are re-exported by cgp, include:

cgp_type!( Runtime );

#[cgp_getter {
    provider: RuntimeGetter,
}]
pub trait HasRuntime: HasRuntimeType {
    fn runtime(&self) -> &Self::Runtime;
}

Stay tuned for updates to the CGP Patterns book for more information on using pluggable async runtimes with CGP.

Future Work

There are several additional features and improvements I had hoped to include in this update. However, with my New Year vacation coming to an end, I need to wrap up the current progress. Below are some of the tasks deferred to future updates.

Documenting the cgp Crate

While the CGP Patterns book offers extensive conceptual coverage, the cgp crate currently lacks comprehensive Rustdoc documentation. Many constructs remain undocumented, and users must rely on the CGP Patterns book or this website for detailed guidance.

In future updates, I plan to add concise Rustdoc comments to these constructs and include links to relevant chapters in the CGP Patterns book. This will help bridge the gap and provide in-crate documentation to enhance usability. For now, all detailed information about CGP is accessible only through the book and website.

Tutorials with More Complex Use Cases

During the launch announcement, many readers noted the lack of practical examples demonstrating how CGP can address more complex, real-world problems. While I had planned to create such tutorials, much of my time was spent completing relevant chapters and updating the cgp crate. I ask for your patience as I work on delivering concise, compelling examples to better illustrate CGP's utility.

In the meantime, the simplified examples in the recently added Associated Types, Error Handling, and Field Accessors chapters provide a glimpse into CGP's practical applications. These include examples like validating whether an authentication token has expired and making HTTP API calls to fetch messages. While not exhaustive, these examples go beyond the basic "Hello World" tutorial on the homepage and offer a clearer picture of how CGP can be applied to your projects.

Acknowledgement

A big thank you to @marvin-hansen for his enthusiastic involvement in discussions, testing CGP with real-world projects, and providing invaluable feedback! The implementation of the #[cgp_getter] and #[cgp_auto_getter] macros was primarily motivated by his input, highlighting that the direct use of HasField could be too complex for beginners. Thanks to his suggestions, CGP now offers a more seamless and intuitive experience for declaring and using field accessor traits.

Thanks also to everyone who contributed feedback to the launch announcement on Reddit and Lobsters! Your insights have been incredibly helpful in shaping the direction of CGP and prioritizing upcoming work. There’s still a long journey ahead before CGP reaches v1.0 and is ready for widespread use, and I appreciate your continued support as we work toward that goal!