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!