A new programming paradigm for Rust

Modular component system

Highly expressive code

Type-Safe composition

No-Std friendly

Zero-Cost abstraction

Explore More ⇩

Announcement

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!

Read the blog post for more details: CGP Updates: v0.3.0 Release and New Chapters.

Introduction

Context-generic programming (CGP) is a new programming paradigm for Rust that allows strongly-typed components to be implemented and composed in a modular, generic, and type-safe way.

On this homepage, we provide a quick overview and highlight the key features of CGP. For a deeper dive into the concepts and patterns of CGP, explore our comprehensive book, Context-Generic Programming Patterns.

Current Status

As of the start of 2025, CGP remains in its early stages of development. While promising, it still has several rough edges, particularly in areas such as documentation, tooling, debugging techniques, community support, and ecosystem maturity.

As such, adopting CGP for serious projects comes with inherent challenges, and users are advised to proceed at their own risk. The primary risk is not technical but stems from the limited support available when encountering difficulties in learning or applying CGP.

At this stage, CGP is best suited for early adopters and potential contributors who are willing to experiment and help shape its future.

Hello World Example

We will demonstrate various concepts of CGP with a simple hello world example.

Greeter Component

To begin, we import the cgp crate and define a greeter component as follows:

use cgp::prelude::*;

#[cgp_component {
    name: GreeterComponent,
    provider: Greeter,
}]
pub trait CanGreet {
    fn greet(&self);
}

The cgp crate provides common constructs through its prelude module, which should be imported in most cases. The first CGP construct we use here is the #[cgp_component] macro. This macro generates additional CGP constructs for the greeter component.

The target of this macro, CanGreet, is a consumer trait used similarly to regular Rust traits. However, unlike traditional traits, we won't implement anything directly on this trait.

The name argument, GreeterComponent, specifies the name of the greeter component. The provider argument, Greeter, designates a provider trait for the component. The Greeter provider is used to define the actual implementations for the greeter component. It has a similar structure to CanGreet, but with the implicit Self type replaced by a generic Context type.

A Name Dependency

Next, let's introduce dependencies needed to implement an example provider for Greeter. We'll start by declaring an abstract Name type:

cgp_type!( Name )

Here, the cgp_type! macro defines a CGP component for the abstract type Name. This macro is a concise alternative to using #[cgp_component]. It also derives additional implementations useful later. For now, it is enough to know that cgp_type! generates a HasNameType consumer trait, which includes an associated type called Name.

Now, we'll define an accessor trait to retrieve the name value from a context:

#[cgp_auto_getter]
pub trait HasName: HasNameType {
    fn name(&self) -> &Self::Name;
}

The HasName trait inherits from HasNameType to access the abstract type Self::Name. It includes the method name, which returns a reference to a value of type Self::Name.

The #[cgp_auto_getter] attribute macro applied to HasName automatically generates a blanket implementation. This enables any context containing a field named name of type Self::Name to automatically implement the HasName trait.

Hello Greeter

The traits CanGreet, HasNameType, and HasName can be implemented independently across different modules or crates. However, we can import them into a single location and then implement a Greeter provider that uses HasName in its implementation:

pub struct GreetHello;

impl<Context> Greeter<Context> for GreetHello
where
    Context: HasName,
    Context::Name: Display,
{
    fn greet(context: &Context) {
        println!("Hello, {}!", context.name());
    }
}

Here, we define GreetHello as a struct, which will be used to implement the Greeter provider trait. Unlike the consumer trait, where Self refers to the implementing type, the Greeter provider trait uses an explicit generic type Context, which fulfills the role of Self from CanGreet. The GreetHello type will serve as the Self type for Greeter, but we don't reference self in the provider trait implementation.

The implementation comes with two additional constraints:

Notice that these constraints are specified only in the impl block, not in the trait bounds for CanGreet or Greeter. This design allows us to use dependency injection for both values and types through Rust’s trait system.

In the greet method, we use context: &Context as a parameter instead of the &self argument used in CanGreet::greet. We then call context.name() to retrieve the name value from the context and print a greeting with println!.

The GreetHello provider implementation is generic over any Context and Context::Name types, as long as they satisfy the corresponding constraints for HasName and Display. By separating the provider trait from the consumer trait, we can have multiple provider implementations like GreetHello that are all generic over any Context, without causing the overlapping implementation issues typically imposed by Rust's trait system.

Person Context

Next, we define a concrete context, Person, and wire it up to use GreetHello for implementing CanGreet:

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

pub struct PersonComponents;

impl HasComponents for Person {
    type Components = PersonComponents;
}

delegate_components! {
    PersonComponents {
        NameTypeComponent: UseType<String>,
        GreeterComponent: GreetHello,
    }
}

The Person context is defined as a struct containing a name field of type String. We use the #[derive(HasField)] macro to automatically derive HasField implementations for every field in Person. This works together with the blanket implementation generated by #[cgp_auto_getter] for HasName, allowing HasName to be automatically implemented for Person without requiring any additional code.

Next, we declare a struct, PersonComponents, which is used to wire up the provider components for Person. We then implement HasComponents for Person, using PersonComponents to indicate that Person will utilize the providers specified in PersonComponents.

We use the delegate_components! macro to wire up PersonComponents with the necessary components. The first mapping, NameTypeComponent: UseType<String>, is a shorthand to associate the abstract Name type with String. The second mapping, GreeterComponent: GreetHello, indicates that we want to use GreetHello as the implementation of the CanGreet consumer trait.

The expressive use of delegate_components! makes it easy to rewire the components for Person. For instance, if we want to use a custom FullName struct for the name type, we can rewire NameTypeComponent to UseType<FullName>. Similarly, if there’s an alternative Greeter provider, GreetLastName, that implements Greeter with additional constraints, we can simply rewire GreeterComponent to use GreetLastName and add any necessary additional wiring to meet those constraints.

It’s important to note that CGP allows component wiring to be done lazily, meaning any errors (such as unsatisfied constraints) will only be detected when a consumer trait is actually used.

Calling Greet

Now that the wiring is set up, we can construct a Person instance and call greet on it:

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

    // prints "Hello, Alice!"
    person.greet();
}

This is made possible by a series of blanket implementations generated by CGP. Here's how the magic works:

There’s quite a bit of indirection happening behind the scenes!

By the end of this tutorial, you should have a high-level understanding of how programming in CGP works. There's much more to explore regarding how CGP handles the wiring behind the scenes, as well as the many features and capabilities CGP offers. To dive deeper, check out our book Context-Generic Programming Patterns.

Key Features

This section highlights some of the key advantages that Context-Generic Programming (CGP) offers.

Modular Component System

CGP leverages Rust's powerful trait system to define generic component interfaces that decouple the code that consumes an interface from the code that implements it. This is achieved by introducing:

By separating provider traits from consumer traits, CGP enables multiple context-generic provider implementations to coexist. This approach circumvents Rust's usual limitation on overlapping or orphaned trait implementations, offering greater flexibility and modularity.

Highly Expressive Code

CGP empowers developers to write abstract programs that are generic over a context, including all its associated types and methods. This capability eliminates the need to explicitly specify an extensive list of generic parameters in type signatures, streamlining code structure and readability.

Additionally, CGP offers powerful macros for defining component interfaces and simplifies the process of wiring component implementations for use with a specific context.

With CGP, Rust code can achieve a level of expressiveness comparable to, if not exceeding, that of other popular programming paradigms, such as object-oriented programming and dynamically typed programming.

Type-Safe Composition

CGP leverages Rust's robust type system to guarantee that all component wiring is type-safe, ensuring that any missing dependencies are caught at compile time. It operates entirely within safe Rust, avoiding dynamic typing techniques such as dyn traits, Any, or runtime reflection.

This strict adherence to type safety ensures that no CGP-specific errors can occur during application runtime, providing developers with greater confidence in their code's reliability.

No-Std Friendly

CGP enables the creation of fully abstract programs that can be defined without relying on any concrete dependencies — except for other abstract CGP components. This abstraction extends to dependencies such as I/O, runtime, cryptographic operations, and encoding schemes, allowing these concerns to be separated from the core application logic.

As a result, the core logic of an application can be seamlessly instantiated with specialized dependencies, making it compatible with no-std environments. These include embedded systems, operating system kernels, sandboxed environments like WebAssembly, and symbolic execution platforms such as Kani.

Zero-Cost Abstraction

CGP operates entirely at compile-time, leveraging Rust's type system to ensure correctness without introducing runtime overhead. This approach upholds Rust's hallmark of zero-cost abstraction, enabling developers to use CGP's features without sacrificing runtime performance.

Problems Solved

Here are some common problems in Rust that CGP helps to address.

Error Handling

Rather than being tied to a specific error crate like anyhow or eyre, CGP's HasErrorType and CanRaiseError traits allow the decoupling of core application logic from error handling. This enables concrete applications to choose their preferred error library and select the error-handling strategy that best suits their needs, such as deciding whether or not to include stack traces in errors.

For more detailed information on error handling, refer to the error handling chapter in our book

Async Runtime

Rather than committing to a specific runtime crate like tokio or async-std, CGP enables the application core logic to rely on an abstract runtime context that provides only the features required by the application.

Unlike monolithic runtime traits, an abstract runtime context in CGP does not require a comprehensive or upfront design of all possible runtime features any application might need. This flexibility allows easy switching between concrete runtime implementations, depending on the specific runtime features the application utilizes.

Overlapping Implementations

A common frustration among Rust programmers is the restriction on overlapping trait implementations. A typical workaround is to use newtype wrappers, but this can become cumbersome when dealing with multiple composite types that need to be extended.

Rust requires a crate to own either the type or the trait for a trait implementation, which often places a significant burden on the author of a new type to implement all the common traits their users might need. This can lead to bloated type definitions, with excessive trait implementations such as Eq, Clone, TryFrom, Hash, and Serialize. Despite careful design, libraries may still face requests from users to implement less common traits, which can only be implemented by the crate that owns the type.

With the introduction of provider traits, CGP removes these restrictions on overlapping implementations. Both the owner and non-owners of a type can define custom implementations for that type. When multiple provider implementations are available, users can choose one and wire it up easily using CGP constructs.

CGP also favors the use of abstract types over newtype wrappers. For instance, a type like f64 can be directly used for both Context::Distance and Context::Weight, with the associated types still treated as distinct within the abstract code. CGP also enables specialized provider implementations, even if the crate does not own the primitive type (e.g., f64) or the provider trait.

Dynamic Dispatch

A common approach for newcomers to support polymorphism in Rust is to use dynamic dispatch with dyn Trait objects. However, this severely limits the functionality to a restricted subset of dyn-compatible (object-safe) features in Rust. Often, this limitation spreads throughout the entire codebase, requiring non-trivial workarounds for non-dyn-compatible constructs, such as Clone.

Even when dynamic dispatch is not used, many Rust programmers rely on ad-hoc polymorphism, defining enums to represent all potential variants of types in the application. This results in numerous match expressions scattered across the codebase, making it difficult to decouple logic for each branch. Additionally, adding new variants to the enum becomes challenging, as every branch must be updated, even when the new variant is only used in a small portion of the code.

CGP provides several solutions to address the dynamic dispatch problem by delegating the "assembly" of the variant collection to the concrete context. The core application logic can be written generically over the context and the associated type representing the abstract enum. CGP also facilitates powerful datatype-generic patterns that allow providers for each variant to be implemented separately and combined to work with enums that contain any combination of variants.

Monolithic Traits

Even without CGP, Rust's trait system provides powerful mechanisms for building abstractions that would be difficult to achieve in other mainstream languages. One common best practice is to write abstract code that is generic over a context type, but this often involves an implicit trait bound tied directly to the generic context.

Unlike CGP, traits in this pattern are typically designed as monolithic, encompassing all the dependencies that the core application might need. Without CGP, an abstract caller must also include all trait bounds required by the generic functions it invokes. As a result, any additional generic trait bounds tend to propagate throughout the codebase, leading developers to combine all these trait bounds into one monolithic trait for convenience.

Monolithic traits can quickly become bottlenecks that prevent large projects from scaling. It's not uncommon for such traits to become bloated with dozens or even hundreds of methods and types. This overgrowth makes it increasingly difficult to introduce new implementations or modify existing ones. Additionally, with Rust's current practices, breaking down or decoupling these monolithic traits into smaller, more manageable traits can be challenging.

CGP offers significant improvements over this traditional pattern, making it possible to write abstract Rust code without the risk of creating unwieldy, monolithic traits. CGP enables the decomposition of large traits into many small, focused traits, each ideally consisting of just a single method or type. This is made possible by the dependency injection pattern used in CGP, which allows implementations to introduce only the minimal trait bounds they need directly within the implementation, rather than bundling everything into a single, monolithic structure.

Getting Started

To get started with CGP, the best approach is to dive into our book, Context-Generic Programming Patterns. It provides a comprehensive guide to understanding and working with CGP.

You can also explore real-world applications and projects that use CGP, such as Hermes SDK, to gain a deeper understanding of its practical uses.

Additionally, be sure to check out the Resources page for more materials and learning tools to help you get up to speed with CGP.

Contribution

We welcome contributors who are passionate about promoting CGP within the Rust ecosystem. Whether you're a beginner or an experienced Rust developer, there are numerous ways you can contribute to the project.

In this section, we'll explore different ways you can get involved and help grow the CGP community. Your contributions, regardless of your level of expertise, are valuable and appreciated!

Read The Documentation

We encourage you to explore the documentation available on this website, including the CGP Patterns book. Your feedback is invaluable to us—if you encounter anything confusing or unclear, please let us know so we can improve the content and make it more accessible to everyone.

Participate in Discussions

Join the conversation on platforms like GitHub or Reddit. Whether you have questions about CGP or ideas for new topics or content, these forums are great places to share your thoughts and engage with the community.

Spread on Social Media

Help raise awareness of CGP by sharing it on social media. Follow our official BlueSky account @contextgeneric.dev to stay updated on CGP’s development and latest news.

Write About It

If you find CGP interesting, consider writing your own blog posts or tutorials to share your learning journey. Sharing your insights can help others learn CGP in different ways, and even if the topic is already covered on the official site, your perspective might make it clearer to others.

Contribute to Design

CGP currently lacks a logo, and our website uses a simple Zola theme. If you have design experience and want to contribute, we would greatly appreciate your help in enhancing the website's design.

Additionally, we have a limited personal budget for professional design work. If you know a designer who could assist us, please feel free to recommend them.

Acknowledgement

CGP was created by Soares Chen, with inspiration drawn from various programming languages and paradigms, particularly Haskell typeclasses.

The development of CGP would not have been possible without the strong support of my employer, Informal Systems. CGP was initially introduced and refined as part of the Hermes SDK project, which leverages CGP to build a highly modular relayer for inter-blockchain communication.

(p.s. We are hiring Rust engineers to work on Hermes SDK and CGP!)