diff --git a/code/Cargo.lock b/code/Cargo.lock index f065c37..ce4d140 100644 --- a/code/Cargo.lock +++ b/code/Cargo.lock @@ -5,7 +5,7 @@ version = 4 [[package]] name = "cgp" version = "0.5.0" -source = "git+https://github.com/contextgeneric/cgp.git?branch=v0.5.0-release#4710c842be969976aef318bc7e5b805f2b415df1" +source = "git+https://github.com/contextgeneric/cgp.git#3b8befaffb2d2624cba32f2a62359040c59a8ba1" dependencies = [ "cgp-core", "cgp-extra", @@ -14,7 +14,7 @@ dependencies = [ [[package]] name = "cgp-async-macro" version = "0.5.0" -source = "git+https://github.com/contextgeneric/cgp.git?branch=v0.5.0-release#4710c842be969976aef318bc7e5b805f2b415df1" +source = "git+https://github.com/contextgeneric/cgp.git#3b8befaffb2d2624cba32f2a62359040c59a8ba1" dependencies = [ "proc-macro2", "quote", @@ -24,12 +24,12 @@ dependencies = [ [[package]] name = "cgp-component" version = "0.5.0" -source = "git+https://github.com/contextgeneric/cgp.git?branch=v0.5.0-release#4710c842be969976aef318bc7e5b805f2b415df1" +source = "git+https://github.com/contextgeneric/cgp.git#3b8befaffb2d2624cba32f2a62359040c59a8ba1" [[package]] name = "cgp-core" version = "0.5.0" -source = "git+https://github.com/contextgeneric/cgp.git?branch=v0.5.0-release#4710c842be969976aef318bc7e5b805f2b415df1" +source = "git+https://github.com/contextgeneric/cgp.git#3b8befaffb2d2624cba32f2a62359040c59a8ba1" dependencies = [ "cgp-async-macro", "cgp-component", @@ -42,7 +42,7 @@ dependencies = [ [[package]] name = "cgp-dispatch" version = "0.5.0" -source = "git+https://github.com/contextgeneric/cgp.git?branch=v0.5.0-release#4710c842be969976aef318bc7e5b805f2b415df1" +source = "git+https://github.com/contextgeneric/cgp.git#3b8befaffb2d2624cba32f2a62359040c59a8ba1" dependencies = [ "cgp-core", "cgp-handler", @@ -52,7 +52,7 @@ dependencies = [ [[package]] name = "cgp-error" version = "0.5.0" -source = "git+https://github.com/contextgeneric/cgp.git?branch=v0.5.0-release#4710c842be969976aef318bc7e5b805f2b415df1" +source = "git+https://github.com/contextgeneric/cgp.git#3b8befaffb2d2624cba32f2a62359040c59a8ba1" dependencies = [ "cgp-component", "cgp-macro", @@ -62,7 +62,7 @@ dependencies = [ [[package]] name = "cgp-error-extra" version = "0.5.0" -source = "git+https://github.com/contextgeneric/cgp.git?branch=v0.5.0-release#4710c842be969976aef318bc7e5b805f2b415df1" +source = "git+https://github.com/contextgeneric/cgp.git#3b8befaffb2d2624cba32f2a62359040c59a8ba1" dependencies = [ "cgp-core", ] @@ -70,7 +70,7 @@ dependencies = [ [[package]] name = "cgp-extra" version = "0.5.0" -source = "git+https://github.com/contextgeneric/cgp.git?branch=v0.5.0-release#4710c842be969976aef318bc7e5b805f2b415df1" +source = "git+https://github.com/contextgeneric/cgp.git#3b8befaffb2d2624cba32f2a62359040c59a8ba1" dependencies = [ "cgp-core", "cgp-dispatch", @@ -86,7 +86,7 @@ dependencies = [ [[package]] name = "cgp-extra-macro" version = "0.5.0" -source = "git+https://github.com/contextgeneric/cgp.git?branch=v0.5.0-release#4710c842be969976aef318bc7e5b805f2b415df1" +source = "git+https://github.com/contextgeneric/cgp.git#3b8befaffb2d2624cba32f2a62359040c59a8ba1" dependencies = [ "cgp-extra-macro-lib", "syn", @@ -95,7 +95,7 @@ dependencies = [ [[package]] name = "cgp-extra-macro-lib" version = "0.5.0" -source = "git+https://github.com/contextgeneric/cgp.git?branch=v0.5.0-release#4710c842be969976aef318bc7e5b805f2b415df1" +source = "git+https://github.com/contextgeneric/cgp.git#3b8befaffb2d2624cba32f2a62359040c59a8ba1" dependencies = [ "proc-macro2", "quote", @@ -105,7 +105,7 @@ dependencies = [ [[package]] name = "cgp-field" version = "0.5.0" -source = "git+https://github.com/contextgeneric/cgp.git?branch=v0.5.0-release#4710c842be969976aef318bc7e5b805f2b415df1" +source = "git+https://github.com/contextgeneric/cgp.git#3b8befaffb2d2624cba32f2a62359040c59a8ba1" dependencies = [ "cgp-component", "cgp-type", @@ -114,7 +114,7 @@ dependencies = [ [[package]] name = "cgp-field-extra" version = "0.5.0" -source = "git+https://github.com/contextgeneric/cgp.git?branch=v0.5.0-release#4710c842be969976aef318bc7e5b805f2b415df1" +source = "git+https://github.com/contextgeneric/cgp.git#3b8befaffb2d2624cba32f2a62359040c59a8ba1" dependencies = [ "cgp-field", ] @@ -122,7 +122,7 @@ dependencies = [ [[package]] name = "cgp-handler" version = "0.5.0" -source = "git+https://github.com/contextgeneric/cgp.git?branch=v0.5.0-release#4710c842be969976aef318bc7e5b805f2b415df1" +source = "git+https://github.com/contextgeneric/cgp.git#3b8befaffb2d2624cba32f2a62359040c59a8ba1" dependencies = [ "cgp-core", ] @@ -130,7 +130,7 @@ dependencies = [ [[package]] name = "cgp-macro" version = "0.5.0" -source = "git+https://github.com/contextgeneric/cgp.git?branch=v0.5.0-release#4710c842be969976aef318bc7e5b805f2b415df1" +source = "git+https://github.com/contextgeneric/cgp.git#3b8befaffb2d2624cba32f2a62359040c59a8ba1" dependencies = [ "cgp-macro-lib", "syn", @@ -139,7 +139,7 @@ dependencies = [ [[package]] name = "cgp-macro-lib" version = "0.5.0" -source = "git+https://github.com/contextgeneric/cgp.git?branch=v0.5.0-release#4710c842be969976aef318bc7e5b805f2b415df1" +source = "git+https://github.com/contextgeneric/cgp.git#3b8befaffb2d2624cba32f2a62359040c59a8ba1" dependencies = [ "itertools", "prettyplease", @@ -151,7 +151,7 @@ dependencies = [ [[package]] name = "cgp-monad" version = "0.5.0" -source = "git+https://github.com/contextgeneric/cgp.git?branch=v0.5.0-release#4710c842be969976aef318bc7e5b805f2b415df1" +source = "git+https://github.com/contextgeneric/cgp.git#3b8befaffb2d2624cba32f2a62359040c59a8ba1" dependencies = [ "cgp-core", "cgp-handler", @@ -160,7 +160,7 @@ dependencies = [ [[package]] name = "cgp-run" version = "0.5.0" -source = "git+https://github.com/contextgeneric/cgp.git?branch=v0.5.0-release#4710c842be969976aef318bc7e5b805f2b415df1" +source = "git+https://github.com/contextgeneric/cgp.git#3b8befaffb2d2624cba32f2a62359040c59a8ba1" dependencies = [ "cgp-core", ] @@ -168,7 +168,7 @@ dependencies = [ [[package]] name = "cgp-runtime" version = "0.5.0" -source = "git+https://github.com/contextgeneric/cgp.git?branch=v0.5.0-release#4710c842be969976aef318bc7e5b805f2b415df1" +source = "git+https://github.com/contextgeneric/cgp.git#3b8befaffb2d2624cba32f2a62359040c59a8ba1" dependencies = [ "cgp-core", ] @@ -176,7 +176,7 @@ dependencies = [ [[package]] name = "cgp-type" version = "0.5.0" -source = "git+https://github.com/contextgeneric/cgp.git?branch=v0.5.0-release#4710c842be969976aef318bc7e5b805f2b415df1" +source = "git+https://github.com/contextgeneric/cgp.git#3b8befaffb2d2624cba32f2a62359040c59a8ba1" dependencies = [ "cgp-component", "cgp-macro", diff --git a/code/Cargo.toml b/code/Cargo.toml index d66cf05..ac14de8 100644 --- a/code/Cargo.toml +++ b/code/Cargo.toml @@ -15,4 +15,4 @@ path = "bin/abstract_name.rs" cgp = { version = "0.5.0" } [patch.crates-io] -cgp = { git = "https://github.com/contextgeneric/cgp.git", branch = "v0.5.0-release" } +cgp = { git = "https://github.com/contextgeneric/cgp.git" } diff --git a/code/bin/hello.rs b/code/bin/hello.rs index efc3366..a39e1c0 100644 --- a/code/bin/hello.rs +++ b/code/bin/hello.rs @@ -14,28 +14,25 @@ pub trait HasName { } // Implement `Greeter` that is generic over `Context` -#[cgp_new_provider] -impl Greeter for GreetHello +#[cgp_impl(new GreetHello)] +impl Greeter for Context where Context: HasName, // Inject the `name` dependency from `Context` { - fn greet(context: &Context) { - // `self` is replaced by `context` inside providers - println!("Hello, {}!", context.name()); + fn greet(&self) { + println!("Hello, {}!", self.name()); } } // A concrete context that uses CGP components -#[cgp_context] #[derive(HasField)] // Deriving `HasField` automatically implements `HasName` pub struct Person { pub name: String, } -// Compile-time wiring and checking of CGP components -delegate_and_check_components! { - CanUsePerson for Person; - PersonComponents { +// Compile-time wiring of CGP components +delegate_components! { + Person { GreeterComponent: GreetHello, // Use `GreetHello` to provide `Greeter` } } diff --git a/content/_index.md b/content/_index.md index c4b2bc0..9d8bf7f 100644 --- a/content/_index.md +++ b/content/_index.md @@ -10,9 +10,12 @@ sort_by = "weight" # Announcement -I am thrilled to announce the release of [**CGP v0.5.0**](/blog/v0-5-0-release/)! This new release includes many exciting features and improvements, including auto dispatchers with `#[cgp_auto_dispatch]`, extensible datatype improvements, monadic computation, emulation of return type notation (RTN), sneak preview of `cgp-serde`, and more. +I’m excited to announce the release of [**CGP v0.6.0**](/blog/v0-6-0-release/)! +This version introduces major ergonomic improvements that make provider and context implementations simpler and more intuitive to write. -[Read the announcement blog post](/blog/v0-5-0-release/) to find out more. +The new `#[cgp_impl]` and `#[cgp_inherit]` macros replace `#[cgp_provider]` and `#[cgp_context]`, offering cleaner syntax and greatly improving the readability of CGP code. + +[Read the announcement blog post](/blog/v0-6-0-release/) to find out more. # Overview diff --git a/content/blog/2025-10-26-v0.6.0-release.md b/content/blog/2025-10-26-v0.6.0-release.md new file mode 100644 index 0000000..02c6e2d --- /dev/null +++ b/content/blog/2025-10-26-v0.6.0-release.md @@ -0,0 +1,338 @@ ++++ + +title = "CGP v0.6.0 Release - Major ergonomic improvements for provider and context implementations" + +authors = ["Soares Chen"] + ++++ + +# Overview + +It has only been two weeks since [v0.5.0 was released](/blog/v0-5-0-release/), yet we are already introducing another major update. [**CGP v0.6.0**](https://github.com/contextgeneric/cgp/releases/tag/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: + +```rust +#[cgp_component(HttpRequestFetcher)] +pub trait CanFetchHttpRequest: HasErrorType { + fn fetch_http_request(&self, request: Request) -> Result; +} +``` + +With `#[cgp_impl]`, you can now implement a provider like this: + +```rust +#[cgp_impl(new FetchWithHttpClient)] +impl HttpRequestFetcher for Context +where + Self: HasHttpClient + HasErrorType, +{ + fn fetch_http_request(&self, request: Request) -> Result { + ... + } +} +``` + +Previously, the same functionality required using `#[cgp_provider]`, which looked like this: + +```rust +#[cgp_new_provider] +impl HttpRequestFetcher for FetchWithHttpClient +where + Context: HasHttpClient + HasErrorType, +{ + fn fetch_http_request(context: &Context, request: Request) -> Result { + ... + } +} +``` + + +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: + +```rust +pub struct App { ... } + +delegate_components! { + App { + FooComponent: FooProvider, + BarComponent: BarProvider, + ... + } +} +``` + +Previously, it was necessary to use a separate provider for the context: + +```rust +#[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: + +```rust +#[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, + } +} + +// 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: + +```rust +#[cgp_provider] +impl CountGetter 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`](https://doc.rust-lang.org/std/hash/trait.Hash.html) trait with `#[cgp_component]`: + +```rust +#[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: + +```rust +#[cgp_impl(HashWithDisplay)] +impl HashProvider for T { + ... +} +``` + +You can then reuse this implementation on any type using `delegate_components!`: + +```rust +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: + +```rust +impl HasName for Context +where + Context: DelegateComponent, + Context::Delegate: NameGetter, +{ + fn name(&self) -> &str { + Context::Delegate::name(self) + } +} +``` + +Previously, the generated code looked like this: + +```rust +impl HasName for Context +where + Context: HasCgpProvider, + Context::CgpProvider: NameGetter, +{ + 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` implementations for all component names, preserving compatibility with existing code. + +For instance, consider the following context definition: + +```rust +#[cgp_context] +pub struct App { ... } +``` + +The macro now generates the following code: + +```rust +pub struct AppComponents; + +impl DelegateComponent 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: + +```rust +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: + +```rust +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: + +```rust +#[cgp_inherit(MyPreset)] +pub struct App { ... } +``` + +is roughly equivalent to the previous way of expressing preset inheritance with `#[cgp_context]`: + +```rust +#[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: + +```rust +impl DelegateComponent for App +where + Self: MyPreset::IsPreset, + MyPreset::Components: DelegateComponent, +{ + type Delegate = >::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](/blog/images/rustlab-2025.png) + +The time is drawing near, and I will be presenting [**How to Stop Fighting with Coherence and Start Writing Context-Generic Trait Impls**](https://rustlab.it/talks/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](https://github.com/sponsors/soareschen) the development of CGP! diff --git a/content/contribute.md b/content/contribute.md index 7bb26eb..efffa55 100644 --- a/content/contribute.md +++ b/content/contribute.md @@ -47,6 +47,6 @@ I do hope to eventually spend a year or two working full-time on CGP using my *p # Acknowledgement -CGP was created by [Soares Chen](https://maybevoid.com/), with inspiration drawn from various programming languages and paradigms, particularly Haskell typeclasses. +CGP is created by [Soares Chen](https://maybevoid.com/), 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](https://informal.systems/). CGP was initially introduced and refined as part of the [Hermes SDK](https://github.com/informalsystems/hermes-sdk/) project, which leverages CGP to build a highly modular relayer for inter-blockchain communication. diff --git a/content/resources.md b/content/resources.md index f21c63a..ee82674 100644 --- a/content/resources.md +++ b/content/resources.md @@ -8,13 +8,12 @@ weight = 5 # Resources -This page lists some resources related to CGP. More resources will be added as -the project continues to develop. +This page lists some resources related to CGP. More resources will be added as the project continues to develop. ## Crates -- [`cgp`](https://crates.io/crates/cgp) - The main Rust crate that provides the core constructs - for programming using CGP. +- [`cgp`](https://crates.io/crates/cgp) - The main Rust crate that provides the core constructs for programming using CGP. +- [`cgp-error-anyhow`](https://crates.io/crates/cgp-error-anyhow) - A CGP crate for handling modular errors using `anyhow`. ## Tutorials @@ -22,11 +21,10 @@ the project continues to develop. ## Books -- [Context-Generic Programming Patterns](https://patterns.contextgeneric.dev/) - The main - book for learning CGP and understand various design patterns for CGP. +- [Context-Generic Programming Patterns](https://patterns.contextgeneric.dev/) - The main book for learning CGP and understand various design patterns for CGP. ## Projects -- [Hypershell](github.com/contextgeneric/hypershell) - A type-level DSL for shell-scripting in Rust. -- [Hermes SDK](https://github.com/informalsystems/hermes-sdk/) - The first project that - uses CGP for real world applications. +- [CGP Examples](https://github.com/contextgeneric/cgp-examples) - A repository hosting various examples of using CGP. +- [Hypershell](https://github.com/contextgeneric/hypershell) - A type-level DSL for shell-scripting in Rust. +- [Hermes SDK](https://github.com/informalsystems/hermes-sdk/) - The first project that uses CGP for real world applications. diff --git a/content/tutorials/hello.md b/content/tutorials/hello.md index 4c0537b..690e2df 100644 --- a/content/tutorials/hello.md +++ b/content/tutorials/hello.md @@ -27,111 +27,76 @@ In its simplified form, the argument to the macro, `Greeter`, designates a _prov The macro also generates an empty `GreeterComponent` struct, which is used as the _name_ of the greeter component which can be used for the component wiring later on. -## Abstract Name Type - -Next, let's introduce dependencies needed to implement an example provider for `Greeter`. We'll start by declaring an abstract `Name` type: - -```rust -#[cgp_type] -pub trait HasNameType { - type Name; -} -``` - -Here, the `#[cgp_type]` macro is used for abstract type traits that contain only one associated type. This macro is an extension to `#[cgp_component]`, and generates additional CGP constructs to work with abstract types. - -Similar to `#[cgp_component]`, `#[cgp_type]` also generates a provider trait called `NameTypeProvider`, and a component name type called `NameTypeProviderComponent`. - ## Name Getter Now, we will define an _getter trait_ to retrieve the name value from a context: ```rust #[cgp_auto_getter] -pub trait HasName: HasNameType { - fn name(&self) -> &Self::Name; +pub trait HasName { + fn name(&self) -> &str; } ``` -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 `HasName` trait contains the getter method `name`, which returns a `&str` string value. -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. +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 `String` to automatically implement the `HasName` trait. ## Hello Greeter -The traits `CanGreet`, `HasNameType`, and `HasName` can be defined separately 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: +The traits `CanGreet` and `HasName` can be defined separately 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: ```rust -#[cgp_new_provider] -impl Greeter for GreetHello +#[cgp_impl(new GreetHello)] +impl Greeter for Context where Context: HasName, - Context::Name: Display, { - fn greet(context: &Context) { - println!("Hello, {}!", context.name()); + fn greet(&self) { + println!("Hello, {}!", self.name()); } } ``` -We use `#[cgp_new_provider]` to define a new provider, called `GreetHello`, which implements 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`. - -Behind the scene, the macro generates an empty struct named `GreetHello`, which is used as the `Self` type for `Greeter`. However, the type is used as an identifier for wiring later, and we don't reference `self` in the provider trait implementation. - -The implementation comes with two additional constraints: +We use `#[cgp_impl]` to define a new provider, called `GreetHello`, which implements the `Greeter` provider trait. The implementation is written to be **generic** over any `Context` type that implements `HasName`. -- `Context: HasName` ensures the context implements `HasName`. -- `Context::Name: Display` allows the provider to work with any abstract `Name` type, as long as it implements `Display`. +Normally, it would not be possible to write a blanket implementation like this in vanilla Rust, due to it violating the *overlapping* and *orphan* rules of Rust traits. However, the use of `#[cgp_impl]` and the `Greeter` provider trait allows us to **bypass** this restriction. -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. +Behind the scene, the macro generates an empty struct named `GreetHello`, which is used as an *identifier* of the provider that implements the `Greeter` trait. -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. +Notice that the constraint `HasName` is 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. ## Person Context Next, we define a concrete context, `Person`, and wire it up to use `GreetHello` for implementing CanGreet: ```rust -#[cgp_context] #[derive(HasField)] pub struct Person { pub name: String, } ``` -The `Person` context is defined as a struct containing a `name` field of type `String`. We apply `#[cgp_macro]` on the `Person` context, which would generate a `PersonComponents` provider and some wirings to allow CGP components to be used with the context. +The `Person` context is defined as a struct containing a `name` field of type `String`. -We also 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. +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. ## Delegate Components -Next, we want to define some wirings to link up the `GreetHello` that we defined earlier, so that we can use it on the `Person` context. This is done by using the `delegate_and_check_components!` macro as follows: +Next, we want to define some wirings to link up the `GreetHello` that we defined earlier, so that we can use it on the `Person` context. This is done by using the `delegate_components!` macro as follows: ```rust -delegate_and_check_components! { - CanUsePerson for Person; - PersonComponents { - NameTypeProviderComponent: - UseType, +delegate_components! { + Person { GreeterComponent: GreetHello, } } ``` -We use the `delegate_and_check_components!` macro to perform the wiring of `Person` context with the chosen providers for each component that we want to use with `Person`. - -The first line, `CanUsePerson for Person`, generates a `CanUsePerson` _check trait_, which is used for implementing checks that the `Person` context has correctly implemented the consumer traits for the components in the entries, i.e. `NameTypeProviderComponent` and `GreeterComponent`. - -The next line, we set the delegation target to `PersonComponents`, which was generated eariler by `#[cgp_context]`. For each entry in `delegate_components!`, we use the component name type as the key, and the chosen provider as the value. - -The first mapping, `NameTypeProviderComponent: UseType`, makes use of the generic `UseType` provider to implement the provider trait `NameTypeProvider`. The `String` argument to `UseType` is used to implement the associated type `Name`. - -The second mapping, `GreeterComponent: GreetHello`, indicates that we want to use `GreetHello` as the implementation of the `CanGreet` consumer trait. +We use the `delegate_components!` macro to perform the wiring of `Person` context with the chosen providers for each CGP component that we want to use with `Person`. For each entry in `delegate_components!`, we use the component name type as the key, and the chosen provider as the value. -During the wiring, if there is any unsatisfied dependency, such as if `Person` does not contain the necessary `name: String` field, then the errors would be raised by the check trait at the first line. +The mapping `GreeterComponent: GreetHello` indicates that we want to use `GreetHello` as the implementation of the `CanGreet` consumer trait. ## Calling Greet @@ -151,10 +116,9 @@ fn main() { This is made possible by a series of blanket implementations generated by CGP. Here's how the magic works: - We can call `greet` because `CanGreet` is implemented for `Person`. -- `PersonComponents` contains the wiring that uses `GreetHello` as the provider for `GreeterComponent`. -- `GreetHello` implements `Greeter`. +- `Person` contains the `delegate_components!` mapping that uses `GreetHello` as the provider for `GreeterComponent`. +- `GreetHello` implements `Greeter` for `Person`. - `Person` implements `HasName` via the `HasField` implementation. -- `Person::Name` is `String`, which implements `Display`. There’s quite a bit of indirection happening behind the scenes! @@ -168,8 +132,6 @@ Below, we show the full hello world example code, so that you can walk through t ```rust -use core::fmt::Display; - use cgp::prelude::*; // Import all CGP constructs // Derive CGP provider traits and blanket implementations @@ -179,46 +141,33 @@ pub trait CanGreet // Name of the consumer trait fn greet(&self); } -// Declare a CGP abstract type `Name` -#[cgp_type] -pub trait HasNameType { - type Name; -} - // A getter trait representing a dependency for `name` value #[cgp_auto_getter] // Derive blanket implementation -pub trait HasName: HasNameType { - fn name(&self) -> &Self::Name; +pub trait HasName { + fn name(&self) -> &str; } // Implement `Greeter` that is generic over `Context` -#[cgp_new_provider] -impl Greeter for GreetHello +#[cgp_impl(new GreetHello)] +impl Greeter for Context where Context: HasName, // Inject the `name` dependency from `Context` - Context::Name: Display, { - fn greet(context: &Context) { - // `self` is replaced by `context` inside providers - println!("Hello, {}!", context.name()); + fn greet(&self) { + println!("Hello, {}!", self.name()); } } // A concrete context that uses CGP components -#[cgp_context] #[derive(HasField)] // Deriving `HasField` automatically implements `HasName` pub struct Person { pub name: String, } -// Compile-time wiring and checks of CGP components -delegate_and_check_components! { - CanUsePerson for Person; - PersonComponents { - NameTypeProviderComponent: - UseType, // Instantiate the `Name` type to `String` - GreeterComponent: - GreetHello, // Use `GreetHello` to provide `Greeter` +// Compile-time wiring of CGP components +delegate_components! { + Person { + GreeterComponent: GreetHello, // Use `GreetHello` to provide `Greeter` } } @@ -230,4 +179,5 @@ fn main() { // `CanGreet` is automatically implemented for `Person` person.greet(); } + ``` diff --git a/static/blog/images/rustlab-2025.png b/static/blog/images/rustlab-2025.png new file mode 100644 index 0000000..4b775c4 Binary files /dev/null and b/static/blog/images/rustlab-2025.png differ