--- name: cgp description: Skills for Context-Generic Programming (CGP) in Rust --- # Guide to Context-Generic Programming (CGP) in Rust This guide gives you a high level overview on how to work with CGP code in Rust. ## Introduction CGP is a modular programming paradigm that provides workaround on the coherence restrictions in Rust. You can write multiple overlapping or orphan trait implementations implementations in CGP, and then wire them through a concrete context. ## Blanket Traits CGP evolves from the use of blanket trait implementations, also commonly called extension traits. This is widely used in Rust today with examples like `StreamExt` and `IterTools`. But CGP takes this concept and pushes it much further. The core idea of CGP is that we can use the `where` clause of a blanket trait impl to hide the constraints needed from the trait interface. For example, given: ```rust pub trait CanGreet { fn greet(&self); } pub trait HasName { fn name(&self) -> &str; } impl CanGreet for Context where Context: HasName, { fn greet(&self) { println!("Hello, {}!", self.name()); } } ``` The blanket implementation of `CanGreet` above hides the `HasName` dependency from the trait interface. This concept of hiding constraints inside blanket impls is also known as **impl-side dependencies**, or dependency injection. Blanket traits are preferred over generic functions, because they provide cleaner interface and don't require the caller to specify all constraints, in case if the caller is also generic. For example, if `CanGreet` is defined as a generic function instead: ```rust pub fn greet(context: &Context) where Context: HasName, { println!("Hello, {}!", context.name()); } ``` then the all transitive callers of `greet` would also need to specify the `Context: HasName` constraint, which can be tedious to manage. Note that blanket traits are not CGP components, but it is commonly used together with CGP. In particular, it is preferred to start writing generic code as blanket traits instead of generic functions first. And then if there is a need for multiple alternative implementations, we can easily convert the blanket trait into a CGP component later. ## Prelude Almost all CGP constructs are imported through the prelude: ```rust use cgp::prelude::*; ``` The prelude should be imported in all Rust modules that use CGP constructs. You can omit the import of prelude inside example code blocks within documentation. The current version of CGP is v0.7.0. All explanation on this document is based on this version. ## `#[cgp_component]` Macro The `#[cgp_component]` macro is used to enable CGP capabilities on a trait. For example: ```rust #[cgp_component(AreaCalculator)] pub trait CanCalculateArea { fn area(&self) -> f64; } ``` The original trait, i.e. `CanCalculateArea`, is now called a **consumer trait**. A CGP consumer trait is typically named in the verb format, e.g. `CanDoSomething`. We also call the fully expanded constructs a CGP trait, or a CGP component. For example, the full constructs can be called the `AreaCalculator` component. ## Provider Traits The argument to `#[cgp_component]` is the name of the **provider trait**, which is generated by the macro as follows: ```rust pub trait AreaCalculator { fn area(context: &Context) -> f64; } ``` In the provider trait, the original `Self` type is moved to an explicit generic parameter called `Context`. All references to the original `self` or `Self` are converted to refer to `context` or `Context`. The new `Self` position in the provider trait will be implemented by unique and dummy provider types, which will act as the provider's name. A CGP provider trait is typically named in the noun format, e.g. `SomethingDoer`. When no suitable postfix is avaiable, the `Provider` postfix is used instead, e.g. `SomethingProvider`. For example, one can write a blanket implementation for `AreaCalculator` as follows: ```rust pub struct RectangleArea; impl AreaCalculator for RectangleArea where Context: HasRectangleFields, { fn area(context: &Context) -> f64 { context.width() * context.height() } } ``` The provider name `RectangleArea` is defined as a local dummy struct. The implementation of `AreaCalculator` can be generic over any `Context` type that implements `HasRectangleFields`. The usual coherence restrictions don't apply, because the `Self` type `RectangleArea` is owned by the same crate. This allows any number of such blanket provider trait implementations to be defined in any crate. Note that the provider value, i.e. the `self` value, is not used anywhere in the provider trait implementation. This means that the provider struct is effectively a type-level-only entity, with no usable value at runtime. It is a common mistake to attempt to define fields in the provider struct, and attempt to pass or access it during runtime. Such fields will not be accessible at runtime. ## Component Name The macro generates a component name type with a `Component` postfix, i.e.: ```rust pub struct AreaCalculatorComponent; ``` The macro also generates blanket implementations to allow delegation of the implementation of a consumer or provider trait to a different provider, which will be explained later. ## `IsProviderFor` Trait CGP uses `IsProviderFor` as a hack to force the Rust compiler to show the appropriate error message when there is an unsatisfied dependency: ```rust pub trait IsProviderFor {} ``` The trait is used as a dummy marker trait that can be trivially implemented, but is deliberately implemented with additional constraints to capture the dependencies to be shown in compile errors. The earlier example provider trait definition for `AreaCalculator` was a simplification, the actual definition is: ```rust pub trait AreaCalculator: IsProviderFor { fn area(context: &Context) -> f64; } ``` The first argument to `IsProviderFor` is the component name, i.e. `AreaCalculatorComponent`. The second argument is the `Context` type. The third argument captures any additional generic parameters as a tuple. When implementing a provider trait, the provider also needs to implement `IsProviderFor` with the same constraints it uses to implement the provider trait. For example: ```rust impl IsProviderFor for RectangleArea where Context: HasRectangleFields, {} ``` This will ensure that if a concrete context does not implement `HasRectangleFields`, the error will show the missing dependency via `IsProviderFor`. ## `#[cgp_provider]` Macro The `#[cgp_provider]` macro removes the need to manually implement `IsProviderFor`, by auto generating the implementation from the provider impl. The `#[cgp_new_provider]` macro has the same behavior as `#[cgp_provider]`, but also defines the provider struct automatically. For example, the following: ```rust #[cgp_new_provider] impl AreaCalculator for RectangleArea where Context: HasRectangleFields, { ... } ``` is the same as: ```rust pub struct RectangleArea; #[cgp_provider] impl AreaCalculator for RectangleArea where Context: HasRectangleFields, { ... } ``` which is then desugared to: ```rust impl AreaCalculator for RectangleArea where Context: HasRectangleFields, { ... } impl IsProviderFor for RectangleArea where Context: HasRectangleFields, {} ``` Whenever possible, avoid mentioning `IsProviderFor` to the user, and use the simplified provider trait definition. When error messages say that `IsProviderFor` is not implemented, translate it to mean that the provider trait is not implemented. ## `#[cgp_impl]` Macro The `#[cgp_impl]` macro further simplify the definition of provider implementations, to make it look less confusing to readers. For example: ```rust #[cgp_impl(new RectangleArea)] impl AreaCalculator for Context where Self: HasRectangleFields, { fn area(&self) -> f64 { self.width() * self.height() } } ``` is the same as: ```rust #[cgp_new_provider] impl AreaCalculator for RectangleArea where Context: HasRectangleFields, { fn area(context: &Context) -> f64 { context.width() * context.height() } } ``` The `Context` parameter in `#[cgp_impl]` is in the same `Self` position as the consumer trait, to make it look like blanket implementations. The provider name is specified in the attribute argument for `#[cgp_impl]`. An optional `new` keyword can be given to automatically define the provider struct. The macro also allows the use of `self` and `Self` to refer to the generic `Context` value and type. Behind the scenes, the `#[cgp_impl]` macro desugars to `#[cgp_provider]` by moving the `Context` type back to the first generic parameter of the provider trait, and use the given provider name type as the `Self` type. Behind the scenes, all references to `self` or `Self` are automatically converted by `#[cgp_impl]` back to refer to the explicit `context` or `Context`. As previously noted, the `Self` type and `self` value inside `#[cgp_impl]` refers to the `Context` type, *not* the provider struct. There is no provider value accessible during runtime. When the provider implementation targets a generic `Context` type, the `for Context` part can be omitted, and the macro will automatically insert the generic parameter. For example, the earlier example can be further simplified as: ```rust #[cgp_impl(new RectangleArea)] impl AreaCalculator where Self: HasRectangleFields, { fn area(&self) -> f64 { self.width() * self.height() } } ``` The omission of the generic `Context` improves the ergonomic of CGP, as it makes the code looks like OOP class implementation with no generics involved. Whenever possible, use `#[cgp_impl]` to write and explain provider implementations, and omit the generic `Context` type. Avoid showing the user `#[cgp_provider]` or `IsProviderFor`, unless they are needed to explain the internal mechanics of CGP. ## `DelegateComponent` Trait The `DelegateComponent` trait is defined as follows: ```rust pub trait DelegateComponent { type Delegate; } ``` This is mainly used to turn a type implementing `DelegateComponent` into a type-level table. The `Component` generic parameter acts as the "key" type, and the `Delegate` associated type acts as the "value" type to be read from the type-level table. For example, given the following: ```rust impl DelegateComponent for MyComponents { type Delegate = Bar; } ``` The code above "sets" the entry `Foo` in the `MyComponents` table to have `Bar` as the "value" type. ## Provider Trait Delegation The `#[cgp_component]` macro generates a blanket implementation for the provider trait for the example `CanCalculateArea` component earlier: ```rust impl AreaCalculatorProvider for Provider where Provider: DelegateComponent, Provider::Delegate: AreaCalculatorProvider + IsProviderFor, { fn area(context: &Context) -> f64 { Context::Delegate::area(context) } } ``` Essentially, this allows a provider to delegate the implementation of a provider trait to another provider. The blanket implementation use `Provider` as the type-level table. The blanket implementation essentially uses the generated `AreaCalculatorComponent` struct as a key, and reads the entry stored on `Provider`'s type-level table. If the `Delegate` "value" type implements the provider trait `AreaCalculator` for the `Context` type, then `Provider` would automatically also implement the `AreaCalculator` through the blanket implementation. The blanket implementation implements the method and other trait items by forwarding them to the delegated provider. The additional `IsProviderFor` constraint is used to forward the constraints in the provider trait implementation down the delegation chain. If possible, avoid showing the `IsProviderFor` constraint when explaining to the user the high level concepts. Whenever possible, do not show the user the generated blanket implementation for the provider trait. Instead explain using high-level concepts like table lookup. ## Consumer Trait Delegation The `#[cgp_component]` macro generates the following blanket implementation for the example `CanCalculateArea` component earlier: ```rust impl CanCalculateArea for Context where Context: AreaCalculator, { fn area(&self) -> f64 { Context::area(self) } } ``` Essentially, a `Context` type implements the consumer trait `CanCalculateArea`, if it implements the provider trait `AreaCalculator` for itself as the context type. When `DelegateComponent` is used on a `Context` type, it implements the provider trait for the `Context` type if the delegated provider implements the provider trait for the `Context` type. This makes `Context` its own provider. The blanket implementation of the consumer trait then uses that blanket provider trait implementation to implement the consumer trait for the `Context` type. Following the earlier example, this allows the consumer trait to be defined on a custom context such as follows: ```rust pub struct Rectangle { pub width: f64, pub height: f64, } impl HasRectangleFields for Rectangle { fn width(&self) -> f64 { self.width } fn height(&self) -> f64 { self.height } } impl DelegateComponent for Rectangle { type Delegate = RectangleArea; } ``` The example `Rectangle` struct above implements `HasRectangleFields`, and then delegate the implementation of `CanCalculateArea` to the `RectangleArea` provider. This is done by implementing `DelegateComponent` with `AreaCalculatorComponent` used as the "key", and `RectangleArea` used as the "value" for `Rectangle`'s type-level table. With that, `Rectangle` now implements `AreaCalculator` through the blanket implementation of the provider trait. After that, it implements `CanCalculateArea` through the blanket implementation of the consumer trait. Whenever possible, do not show the user the generated blanket implementation for the consumer trait. Instead explain using high-level concepts like table lookup. ## `delegate_components!` Macro The `delegate_components!` macro is commonly used to simplify the definition of type-level tables through the `DelegateComponent` trait. For example, given the following: ```rust delegate_components! { Rectangle { AreaCalculatorComponent: RectangleArea, } } ``` would generate the following `DelegateComponent` and `IsProviderFor` implementation: ```rust impl DelegateComponent for Rectangle { type Delegate = RectangleArea; } impl IsProviderFor for Rectangle where RectangleArea: IsProviderFor {} ``` The first argument to `delegate_components!`, i.e. `Rectangle`, designates the target type where the type-level table is defined, or which the `DelegateComponent` trait will be implemented by. The `IsProviderFor` implementation helps the propagation of the provider trait constraints. This allows a provider to implement a provider trait through `delegate_components!`, and at the same time keep track of the dependencies. Whenever possible, try to avoid mentioning the generation of the `IsProviderFor` implementation inside `delegate_components!`. ## Explicit Delegation It is possible to skip the use of `delegate_components!`, and implement the consumer trait directly on the concrete context. For example, given the following: ```rust delegate_components! { Rectangle { AreaCalculatorComponent: RectangleArea, } } ``` we can instead write: ```rust impl HasArea for Rectangle { fn area(&self) -> f64 { >::area(self) } } ``` The manual delegation is much more verbose, but it is much easier to understand as compared to the use of `delegate_components!`, `DelegateComponent`, and the blanket implementations. When explaining the concepts behind `delegate_components!`, we can use the explicit delegation to demonstrate what the code effectively does, without requiring the reader to fully understand the trait machinery behind CGP. ## Direct Implementation of Consumer Trait Aside from explicit delegation, the user can always implement a consumer trait directly on a concrete context, if they don't care about code reuse: ```rust impl HasArea for Rectangle { fn area(&self) -> f64 { self.width * self.height } } ``` It is important to highlight the possibility of directly implementing a consumer trait. This demonstrates that CGP traits are superset of vanilla Rust traits. And if the user prefers, they can always use or implement it like a regular Rust trait. ### Array Syntax in `delegate_components!` The `delegate_components!` macro also supports array syntax, in case when multiple type-level keys map to the same value. For example: ```rust delegate_components! { MyComponents { [ FooComponent, BarComponent, ]: FooBarProvider, BazComponent: BazProvider, } } ``` is the same as: ```rust delegate_components! { MyComponents { FooComponent: FooBarProvider, BarComponent: FooBarProvider, BazComponent: BazProvider, } } ``` which is eventually desugared to: ```rust impl DelegateComponent for MyComponents { type Delegate = FooBarProvider; } impl DelegateComponent for MyComponents { type Delegate = FooBarProvider; } impl DelegateComponent for MyComponents { type Delegate = BazProvider; } ``` ### `new` in `delegate_components!` The `delegate_components!` macro supports an optional `new` keyword in front of the target table type, to automatically define the type for the user. For example: ```rust delegate_components! { new MyComponents { FooComponent: FooBarProvider, ... } } ``` would also generate a `struct MyComponents;` definition. Whenever possible, do not show the user the use of the `DelegateComponent` trait. Instead explain to them using high level concepts, such as that a type-level table is constructed for `MyComponents` using `delegate_components!`. ## Type-Level List CGP commonly uses type-level lists, a.k.a product types, to represent a list of types. A type-level list is defined as `Product![A, B, C]`, which is desugared as: ```rust π>> ``` or in a human-readable form: ```rust Cons>> ``` The types `Cons` and `Nil` are defined as: ```rust pub struct π(pub Head, pub Tail); pub struct ε; pub use {ε as Nil, π as Cons}; ``` The greek alphabets like `π` and `ε` are used to shorten the representation of these types when displayed by the Rust compiler in places like error messages. Whenever possible, you should prefer the syntactic sugar forms like `Product!` or the human readable forms like `Cons`. ## Type-Level Strings CGP uses type-level strings to represent field names as types, in the form of `Symbol!("string value")`. The macro desugars a type-level string like `Symbol!("abc")` into follows: ```rust ψ<3, ζ<'a', ζ<'b', ζ<'c', ε>>>> ``` or in a readable form: ```rust Symbol<3, Chars<'a', Chars<'b', Chars<'c', Nil>>>> ``` The types `Symbol` and `Chars` are defined as: ```rust pub struct ζ(pub PhantomData); pub struct ψ(pub PhantomData); pub use {ψ as Symbol, ζ as Chars}; ``` The `Chars` type is essentially a short hand for defining a type-level list of characters. The `Symbol` type is used to compute the string length at compile time. This is to workaround the lack of const-generics evaluation in stable Rust. ## `Index` Type CGP supports use of type-level natural numbers through the `Index` type, a.k.a. `δ`, which is defined as: ```rust pub struct δ; pub use δ as Index; ``` The `Index` type can be used to represent indices as types, such as `Index<0>`, `δ<1>`. ## `HasField` Trait The most basic use case for CGP is for dependency injection of getting values from the context. This is done through the `HasField` trait, which is defined as follows: ```rust pub trait HasField { type Value; fn get_field(&self, _tag: PhantomData) -> &Self::Value; } ``` The `Tag` type is used to refer to a field in a struct, such as `Symbol!("name")` or `Index<0>`. The `_tag` parameter with `PhantomData` type is used to assist type inference to inform the Rust compiler of the `Tag` type, in case when multiple `HasField` implementations are in scope. The `HasField` trait can be automatically derived. For example: ```rust #[derive(HasField)] pub struct Rectangle { pub width: f64, pub height: f64, } ``` will generate the following `HasField` impls: ```rust impl HasField for Rectangle { type Value = f64; fn value(&self, _tag: PhantomData) -> &f64 { &self.width } } impl HasField for Rectangle { type Value = f64; fn value(&self, _tag: PhantomData) -> &f64 { &self.height } } ``` The `HasField` trait can also derived for structs with unnamed fields, and uses `Index` to refer to the field indices. For example: ```rust #[derive(HasField)] pub struct Rectangle(f64, f64); ``` will generate: ```rust impl HasField> for Rectangle { type Value = f64; fn value(&self, _tag: PhantomData>) -> &f64 { &self.0 } } impl HasField> for Rectangle { type Value = f64; fn value(&self, _tag: PhantomData>) -> &f64 { &self.1 } } ``` ## Dependency Injection CGP leverages Rust's trait system to enable dependency injection, also called impl-side dependencies. The dependency injection is done in the form of constraints specified only in the `where` clause of `impl` blocks, but not in the trait definition. For example, given: ```rust #[cgp_component(Greeter)] pub trait CanGreet { fn greet(&self); } ``` Using `HasField`, one can perform dependency injection to retrieve a string value from the context, and implement a `Greeter` provider as follows: ```rust pub struct GreetHello; impl Greeter for GreetHello where Context: HasField, { fn greet(context: &Context) { println!("Hello, {}!", context.get_field(PhantomData)); } } ``` This allows `CanGreet` to be implemented on any concrete context struct that derives `HasField` and contains a `name` field of type `String`. For example: ```rust #[derive(HasField)] pub struct Person { pub name: String, } delegate_components! { Person { GreeterComponent: GreetHello, } } ``` The dependency injection technique can also be used in vanilla Rust traits with blanket implementations, such as: ```rust pub trait CanGreet { fn greet(&self); } impl Greeter for Context where Context: HasField, { fn greet(&self) { println!("Hello, {}!", self.get_field(PhantomData)); } } ``` This is commonly used to hide the constraints of one implementation behind a trait interface, without using `#[cgp_component]` to enable multiple alternative implementations. This is useful to simplify the learning curve of CGP, as users can mostly work with vanilla Rust traits. ## `#[cgp_auto_getter]` Macro `#[cgp_auto_getter]` macro provides additional abstraction on top of `HasField`, so that users don't need to understand the internals of `HasField`. For example, given: ```rust #[cgp_auto_getter] pub trait HasName { fn name(&self) -> &String; } ``` the macro would generate the following blanket implementation: ```rust impl HasName for Context where Context: HasField, { fn name(&self) -> &str { self.get_field(PhantomData).as_str() } } ``` The `#[cgp_auto_getter]` macro generates blanket impls that use `HasField` implementations with the field name as the `Tag` type, and the return type as the `Value` type. The macro supports short hand for several return types such as `&str`, to make the `name` method more ergonomic. So we can rewrite the same trait to return `&str` instead of `&String`: ```rust #[cgp_auto_getter] pub trait HasName { fn name(&self) -> &str; } ``` ### Explicit Getter Implementation A getter trait can always be implemented manually, if the concrete struct do not derive `HasField` or don't contain the relevant field. For example, instead of implementing `HasName` automatically: ```rust #[derive(HasField)] pub struct Person { pub name: String } ``` one can opt to not derive `HasField` and implement `Name` explicitly: ```rust pub struct Person { pub name: String } pub trait HasName for Person { fn name(&self) -> &str; } ``` The explicit getter implementation is much more verbose, especially when a context contains many fields. However, it is also much more easier to understand and does not require the user to understand the advanced trait machinery with `HasField` and blanket implementations. The explicit getter implementation can be used to explain the equivalent effect when both `#[cgp_auto_getter]` and `#[derive(HasField)]` are used. It is also important to highlight the possibility of explicit getter implementation, to avoid misunderstanding from the user that CGP getter traits are somehow more magical than vanilla Rust traits. The explicit implementation demonstrates that the only purpose for `#[cgp_auto_getter]` is to save the user from writing some boilerplate. But they can always write such boilerplate if that is their preference. ## `#[cgp_getter]` Macro The `#[cgp_getter]` macro is an extension to `#[cgp_component]` that provides similar feature as `#[cgp_auto_getter]`, but allows the getter field to be customized through CGP. For example, given: ```rust #[cgp_getter] pub trait HasName { fn name(&self) -> &str; } ``` is the same as writing: ```rust #[cgp_component(NameGetter)] pub trait HasName { fn name(&self) -> &str; } ``` but also has the following `UseField` provider implemented: ```rust #[cgp_impl(UseField)] impl NameGetter for Context where Context: HasField, { fn name(&self) -> &str { self.get_field(PhantomData) } } ``` Similar to `#[cgp_auto_getter]`, a `#[cgp_getter]` trait can also be directly implemented on a concrete context. ## `UseField` Pattern CGP defines the `UseField` type as a general target for implementing getter providers by `#[cgp_getter]`. The `UseField` provider accepts a generic `Tag` parameter that represents the name of the field from the context to be used to implement the getter. The `Tag` in `UseField` can use a different name as the getter method, allowing greater flexibility than `#[cgp_auto_getter]` which always require the context to have a field with the exact same name. For example, one can have the following wiring: ```rust #[derive(HasField)] pub struct Person { pub first_name: String, } delegate_components! { Person { NameGetterComponent: UseField, } } ``` the example `UseField` provider will use the `first_name` field in `Person` to implement the `NameGetter::name`. Whenever possible, explain the `UseField` provider by saying that it implements the getter trait by reading from the context the field name specified. For example, `Person` implements `HasName` using its `first_name` field. ## Implicit Arguments CGP supports implicit arguments for implementations to automatically retrieve field values from a context. For example: ```rust #[cgp_component(AreaCalculator)] pub trait CanCalculateArea { fn area(&self) -> f64; } #[cgp_impl(new RectangleArea)] impl AreaCalculator { fn area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { width * height } } ``` The function arguments with `#[implicit]` are automatically extracted and removed from the function signature, and the code desugars to the following: ```rust #[cgp_impl(new RectangleArea)] impl AreaCalculator where Self: HasField + HasField, { fn area(&self) -> f64 { let width: f64 = self.get_field(PhantomData::).clone(); let height: f64 = self.get_field(PhantomData::).clone(); width * height } } ``` The semantics of implicit arguments follows the same pattern as `#[cgp_auto_getter]`. For example, `.clone()` is automatically added to implicit arguments with owned values, and `.as_str()` is automatically added to implicit arguments with with `&str` type. When writing basic CGP code, it is strongly recommended to use implicit arguments whenever possible. This allows the user to write code that looks more like normal Rust code, without the need to understand the underlying trait machinery of `HasField` and getter traits. On the other hand, `#[cgp_auto_getter]` and `#[cgp_getter]` are better suited for more advanced use cases, such as when field access happening in the middle of a function body. `#[cgp_auto_getter]` also reduces the boilerplate when the same fields are repeatedly accessed in multiple functions. ## `#[cgp_fn]` Macro The `#[cgp_fn]` macro supports the use of implicit arguments in plain Rust function syntax, and desugars the code into blanket implementations. For example: ```rust #[cgp_fn] fn rectangle_area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { width * height } ``` desugars to: ```rust pub trait RectangleArea { fn area(&self) -> f64; } impl RectangleArea for Context where Self: HasField + HasField, { fn area(&self) -> f64 { let width: f64 = self.get_field(PhantomData::).clone(); let height: f64 = self.get_field(PhantomData::).clone(); width * height } } ``` This significantly improves the ergonomic and reduces the boilerplate of defining blanket trait implementations. `#[cgp_fn]` exposes the simplest CGP concepts, and only requires the user to understand plain Rust functions to get started. Whenever possible, prefer to use `#[cgp_fn]` over `#[cgp_component]` and `#[cgp_impl]` to explain basic CGP concepts. Then main difference is that `#[cgp_fn]` only allows single implementation, while `#[cgp_component]` allows multiple implementations but requires additional wiring step. ### Custom trait name for `#[cgp_fn]` `#[cgp_fn]` accepts an optional attribute argument to specify the name of the generated trait. If unspecified, it would use the function name in PascalCase as the trait name. For example, the earlier example is equivalent to: ```rust #[cgp_fn(RectangleArea)] fn rectangle_area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { width * height } ``` We can for example change the trait name to `CanCalculateRectangleArea`: ```rust #[cgp_fn(CanCalculateRectangleArea)] fn rectangle_area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { width * height } ``` and the generated trait would be: ```rust pub trait CanCalculateRectangleArea { fn rectangle_area(&self) -> f64; } ``` ## Generics and `where` clause in `#[cgp_fn]` By default, all generic parameters of the function in `#[cgp_fn]` are moved to the generated trait and impl, and the `where` clause is moved to the impl block only. For example: ```rust #[cgp_fn] pub fn rectangle_area( &self, #[implicit] width: Scalar, #[implicit] height: Scalar, ) -> Scalar where Scalar: Mul, { width * height } ``` desugars to: ```rust pub trait RectangleArea { fn rectangle_area(&self) -> Scalar; } impl RectangleArea for Context where Self: HasField + HasField, Scalar: Mul + Clone, { fn rectangle_area(&self) -> Scalar { let width: Scalar = self.get_field(PhantomData::).clone(); let height: Scalar = self.get_field(PhantomData::).clone(); width * height } } ``` - The `Scalar: Clone` bound is in the impl-generics, so it is included in both the trait and impl. - The `Scalar: Mul` bound is only in the `where` clause of the impl. It is an impl-side dependency that is not included in the trait definition. `#[cgp_fn]` specifically do not support the use of generics in the desugared trait method. This is because such use is relatively uncommon in CGP. And even when the need arises, it is considered advanced use case that is better written as explicit blanket implementations, instead of using `#[cgp_fn]`. ## `#[uses]` Attribute The `#[uses]` attribute can be used in both `#[cgp_fn]` and `#[cgp_impl]` to add simple `where` trait bounds to the `Self` context. For example, given: ```rust #[cgp_fn] fn rectangle_area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { width * height } #[cgp_fn] #[uses(RectangleArea)] fn scaled_rectangle_area(&self, #[implicit] scale_factor: f64) -> f64 { self.rectangle_area() * scale_factor * scale_factor } ``` The `scaled_rectangle_area` function is equivalent to: ```rust #[cgp_fn] fn scaled_rectangle_area(&self, #[implicit] scale_factor: f64) -> f64 where Self: RectangleArea, { self.rectangle_area() * scale_factor * scale_factor } ``` which both desugars to: ```rust pub trait ScaledRectangleArea { fn scaled_rectangle_area(&self) -> f64; } impl ScaledRectangleArea for Context where Self: HasField + RectangleArea, { fn scaled_rectangle_area(&self) -> f64 { let scale_factor: f64 = self.get_field(PhantomData::).clone(); self.rectangle_area() * scale_factor * scale_factor } } ``` It is highly recommended to use `#[uses]` over explicit `where` clauses on `Self`, especially when writing basic CGP code. This makes the code looks more like a `use` import statement, which "imports" the dependencies like `RectangleArea` to be used in the function body. This is more intuitive to the use of `where` clause with `Self`, which is often much less intuitive to users who are new to Rust, let alone CGP. The `#[uses]` attribute can be used to import CGP constructs defined with both `#[cgp_fn]` and `#[cgp_component]`. For example: ```rust #[cgp_component(AreaCalculator)] pub trait CanCalculateArea { fn area(&self) -> f64; } #[cgp_fn] #[uses(CanCalculateArea)] pub fn scaled_area(&self, #[implicit] scale_factor: f64) -> f64 { self.area() * scale_factor * scale_factor } ``` This enables scaled area calculation for any context that implements `CanCalculateArea`, regardless of which `AreaCalculator` provider is wired with the context. Conversely, `#[cgp_impl]` can also use `#[uses]` to import dependencies from other `#[cgp_fn]` or `#[cgp_component]` constructs. For example: ```rust #[cgp_fn] fn rectangle_area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { width * height } #[cgp_component(AreaCalculator)] pub trait CanCalculateArea { fn area(&self) -> f64; } #[cgp_impl(new RectangleAreaCalculator)] #[uses(RectangleArea)] impl AreaCalculator { fn area(&self) -> f64 { self.rectangle_area() } } ``` The `#[uses]` attribute is specifically designed to look like a `use` statement, and so it support simplified trait bounds syntax in the form `TraitIdent`. This means that one cannot write more complex bounds like ones that contain associated type equality in it. Instead, if these are needed, they should be written as explicit `where` clauses in the function body. ## `#[extend]` Attribute The `#[extend]` attribute can be used in both `#[cgp_fn]` and `#[cgp_component]` to include the given trait bounds as the super traits of the generated trait. For example, given: ```rust pub trait HasScalarType { type Scalar: Clone + Mul; } #[cgp_fn] #[extend(HasScalarType)] fn rectangle_area( &self, #[implicit] width: Self::Scalar, #[implicit] height: Self::Scalar, ) -> Self::Scalar { width * height } ``` would be desugared to: ```rust pub trait RectangleArea: HasScalarType { fn rectangle_area(&self) -> Self::Scalar; } impl RectangleArea for Context where Self: HasField + HasField + HasScalarType, { fn rectangle_area(&self) -> Self::Scalar { let width: Self::Scalar = self.get_field(PhantomData::).clone(); let height: Self::Scalar = self.get_field(PhantomData::).clone(); width * height } } ``` Note that `#[extend]` is the only way to add supertrait bounds to `#[cgp_fn]`. This is because the `where` clauses in the function body are treated as impl-side dependencies, and thus are hidden from the generated trait definition. `#[extend]` can also be used with `#[cgp_component]` to add supertrait bounds to the generated consumer trait. For example: ```rust #[cgp_component(AreaCalculator)] #[extend(HasScalarType)] pub trait CanCalculateArea { fn area(&self) -> Self::Scalar; } ``` is equivalent to: ```rust #[cgp_component(AreaCalculator)] pub trait CanCalculateArea: HasScalarType { fn area(&self) -> Self::Scalar; } ``` The choice of whether to use `#[extend]` or the normal Rust syntax for super traits is mostly a matter of style. The normal Rust syntax is more concise, but `#[extend]` enables gradual transition to supertraits to users who are getting started with `#[cgp_fn]` and are not yet familiar with Rust's trait system. This is because many Rust developers are not familiar with the supertrait concept, and the appearance of many supertrait bounds can look intimidating. On the other hand, `#[extend]` can be explained as being the `pub use` equivalent of the `#[uses]` attribute, which has a more direct correspondance to the vanilla `use` statement in Rust. ## `#[extend_where]` Attribute The `#[extend_where]` attribute can be used in `#[cgp_fn]` to add `where` clauses to the generated trait definition. For example: ```rust #[cgp_fn] #[extend_where(Scalar: Clone)] fn rectangle_area( &self, #[implicit] width: Scalar, #[implicit] height: Scalar, ) -> Scalar where Scalar: Mul, { width * height } ``` would produce the following trait definition: ```rust pub trait RectangleArea where Scalar: Clone, { fn rectangle_area(&self) -> Scalar; } ``` `#[extend_where]` is not supported in `#[cgp_impl]` or `#[cgp_component]`, because the `where` clauses in these constructs are already part of the trait definition, and thus can be directly written in the normal Rust syntax. ## Abstract Types - CGP supports abstract types by defining associated types in CGP traits. For example: ```rust #[cgp_component(NameTypeProviderComponent)] pub trait HasNameType { type Name; } ``` - The abstract type can be used in another trait interface as the super trait, such as: ```rust #[cgp_auto_getter] pub trait HasName: HasNameType { fn name(&self) -> &Self::Name; } ``` ## `#[cgp_type]` Macro - CGP provides the `#[cgp_type]` macro that can be used in place of `#[cgp_component]` to define abstract type traits. - For example, the `HasNameType` trait can be redefined as: ```rust #[cgp_type] pub trait HasNameType { type Name; } ``` - If no provider name is given in `#[cgp_type]`, a default provider name with the type name plus `TypeProvider` postfix is used. So the above code is the same as: ```rust #[cgp_type(NameTypeProvider)] pub trait HasNameType { type Name; } ``` - `#[cgp_type]` has the same base behavior as `#[cgp_component]`, but generates additional constructs such as a blanket implementation for `UseType`: ```rust #[cgp_impl(UseType)] impl NameTypeProvider { type Name = Name; } ``` ## `UseType` Provider - The `UseType` struct is defined by CGP, which is implemented by providers that use `#[cgp_type]` as a design pattern: ```rust pub struct UseType(pub PhantomData); ``` - The `UseType` pattern allows a concrete context to implement an abstract type by delegating it to `UseType`. For example: ```rust delegate_components! { Person { NameTypeProviderComponent: UseType, } } ``` would implement `HasNameType` for `Person` with `Name` being implemented as `String`. ## Direct Implementation Type Traits - An abstract type can always be directly implemented on a concrete context through its consumer trait. - For example, instead of using `UseType`, we can implement `HasNameType` as follows: ```rust impl HasNameType for Person { type Name = String; } ``` - The direct implementation of a type trait is not much more verbose than the indirect implementation through `delegate_components!` and `UseType`. So it may be preferred especially for simple use cases. - For users who are new to CGP, it is preferred to always show a direct implementation of the type traits. This helps the user to understand that CGP abstract types are nothing more than vanilla Rust traits that contain associated types. ## Abstract Type in Getter Traits - When a getter trait contains only one getter method, it can define a local associated type and use it as the return type of the getter method. For example: ```rust #[cgp_auto_getter] pub trait HasName { type Name; fn name(&self) -> &Self::Name; } ``` - This allows the abstract `Name` type to be automatically inferred based on the `name` field of the concrete context. - This approach is useful when the only purpose of the abstract type is to be used as the return type of the getter method, but not anywhere else. ## Abstract Type import with `#[use_type]` The `#[use_type]` attribute can be used as a more idiomatic way to import abstract types in `#[cgp_fn]`, `#[cgp_impl]`, and `#[cgp_component]`. For example, given: ```rust pub trait HasScalarType { type Scalar: Clone + Mul; } #[cgp_fn] #[use_type(HasScalarType::Scalar)] fn rectangle_area( &self, #[implicit] width: Scalar, #[implicit] height: Scalar, ) -> Scalar { width * height } ``` The code would be desugared to: ```rust pub trait RectangleArea: HasScalarType { fn rectangle_area(&self) -> ::Scalar; } impl RectangleArea for Context where Self: HasField::Scalar> + HasField::Scalar> + HasScalarType, { fn rectangle_area(&self) -> ::Scalar { let width: ::Scalar = self.get_field(PhantomData::).clone(); let height: ::Scalar = self.get_field(PhantomData::).clone(); width * height } } ``` Compared to just including an abstract type in the trait bound or supertrait, `#[use_type]` replaces all occurrences of the abstract type identifier and replaces it with the fully qualified syntax `::Type`. This significantly reduces the boilerplate of adding prefixes like `Self::` to every occurrence of the abstract type. The fully qualified syntax also avoids any potential ambiguity. In particular, it allows nested associated types to be used without needing the user to specify the fully qualified syntax themselves. `#[use_type]` can also be used in both `#[cgp_impl]` and `#[cgp_component]` to import abstract types in the same way. For example: ```rust #[cgp_component(AreaCalculator)] #[use_type(HasScalarType::Scalar)] pub trait CanCalculateArea { fn area(&self) -> Scalar; } #[cgp_impl(new RectangleArea)] #[use_type(HasScalarType::Scalar)] impl AreaCalculator { fn area(&self, #[implicit] width: Scalar, #[implicit] height: Scalar) -> Scalar { width * height } } ``` would first be desugared to the following, before the rest of the desugaring process: ```rust #[cgp_component(AreaCalculator)] pub trait CanCalculateArea: HasScalarType { fn area(&self) -> ::Scalar; } #[cgp_impl(new RectangleArea)] impl AreaCalculator where Self: HasScalarType, { fn area( &self, #[implicit] width: ::Scalar, #[implicit] height: ::Scalar, ) -> ::Scalar { width * height } } ``` Whenever possible, it is strongly recommended to always use `#[use_type]` to import abstract types, for all use cases in `#[cgp_fn]`, `#[cgp_impl]`, and `#[cgp_component]`. This significantly reduces the boilerplate of using abstract types, and makes the code much more readable. It also enables better syntax extension in the future, which would require explicit use of `#[use_type]` to get the necessary metadata for the syntax extension to work. ## Higher Order Providers Higher order providers is a CGP design pattern that allows providers to accept other providers as generic parameters. For example, with the `CanCalculateArea` trait: ```rust #[cgp_component(AreaCalculator)] pub trait CanCalculateArea { fn area(&self) -> f64; } ``` we can define a higher order provider `ScaledArea` as follows: ```rust #[cgp_impl(ScaledArea)] impl AreaCalculator where InnerCalculator: AreaCalculator, { fn area(&self, #[implicit] scale_factor: f64) -> f64 { InnerCalculator::area(self) * scale_factor * scale_factor } } ``` The behavior of the inner area calculation is now determined by the `InnerCalculator` generic parameter, instead of the context. ## `#[use_provider]` Attribute The `#[use_provider]` attribute can be used to improve the ergonomics of using higher order providers, by hiding the `Self` parameter at the first position of the generic parameter of the provider trait. For example, the earlier `ScaledArea` provider can be rewritten as: ```rust #[cgp_impl(ScaledArea)] #[use_provider(InnerCalculator: AreaCalculator)] impl AreaCalculator { fn area(&self, #[implicit] scale_factor: f64) -> f64 { #[use_provider(InnerCalculator)] self.area() * scale_factor * scale_factor } } ``` The outer `#[use_provider]` attribute automatically adds the `Self` parameter to the generic parameter of `InnerCalculator`, so that the user only needs to write `InnerCalculator: AreaCalculator` instead of `InnerCalculator: AreaCalculator`. The trait bound is then added to the `where` clause of the impl block. The inner `#[use_provider]` attribute accepts a `Provider` type and can be applied on a method call expression. It converts the expression from the form `receiver.method(args)` to `Provider::method(receiver, args)`, so that the method call is dispatched to the specified provider instead of through the context. It is strongly recommended to always use `#[use_provider]` when implementing higher order providers, as it significantly reduces the boilerplate of writing higher order providers, and makes the code much more readable. Without it, the reader may be confused by the extra `Self` generic parameter at the first position of the provider trait, which breaks the illusion that the provider trait appears the same as the consumer trait. ## Non-higher-order providers with generic parameters Note that not all providers that contain generic parameters are higher order providers. They only become higher order providers when the generic parameters are used with provider trait constraints in the `where` clause. For example, the following provider is not a higher order provider: ```rust #[cgp_impl(new GetName)] impl NameGetter where Self: HasField, { fn name(&self) -> &str { self.get_field(PhantomData) } } ``` The code above uses the `UseField` pattern, where the `Tag` type is used as the field name to access the corresponding field value via `HasField`. But since there is no constraint for `Tag` to implement any provider trait, the provider `GetName` is not a higher order provider. ## Generic Parameters CGP traits can also contain generic parameters, for example: ```rust #[cgp_component(AreaCalculator)] pub trait CanCalculateArea { fn area(&self, shape: &Shape) -> f64; } ``` defines a further modularized version of the earlier `CanCalculateArea` trait, where the area calculation is done on the generic `Shape` parameter instead of the context. When the provider trait is generated, the generic parameters are appended after the `Context` parameter. For example: ```rust pub trait AreaCalculator: IsProviderFor { fn area(context: &Context, shape: &Shape) -> f64; } ``` In the `IsProviderFor` supertrait, all generic parameters a grouped together into a tuple and placed in the last `Params` position. When the trait contains lifetime generic parameters, they are wrapped in the `Life` type, which lifts lifetimes into types: ```rust pub struct Life<'a>(pub PhantomData<*mut &'a ()>); ``` ## `UseDelegate` Provider For traits containing generic parameters, the `#[cgp_component]` macro supports additional option to generate `UseDelegate` providers that dispatch providers based on the generic type using an inner type-level table. For example, given: ```rust #[cgp_component { provider: AreaCalculator, derive_delegate: UseDelegate, }] pub trait CanCalculateArea { fn area(&self, shape: &Shape) -> f64; } ``` The following provider will be generated: ```rust #[cgp_impl(UseDelegate)] impl AreaCalculator where Components: DelegateComponent, Components::Delegate: AreaCalculator, { fn area(&self, shape: &Shape) -> f64 { Components::Delegate::area(self, shape) } } ``` Only the generic type specified in `UseDelegate`'s generic parameter will be used as the key. For example, the `UseDelegate` provider above dispatches based on `Shape` alone. The `UseDelegate` type is defined by CGP, but one can define and use other delegate providers in similar ways. For example: ```rust pub struct UseInputDelegate(pub PhantomData); #[cgp_component { provider: Computer, derive_delegate: [ UseDelegate, UseInputDelegate, ], }] pub trait CanCompute { type Output; fn compute(&self, _code: PhantomData, input: Input) -> Self::Output; } ``` the `CanCompute` trait above defines two delegate providers. The default `UseDelegate` provider dispatches based on the `Code` type, while the local `UseInputDelegate` provider dispatches based on the `Input` type. ## Nested Table Definition `delegate_components!` supports defining nested type-level tables within the outer table definition. For example: ```rust delegate_components! { MyApp { AreaCalculatorComponent: UseDelegate, ... } } ``` is the same as: ```rust delegate_components! { MyApp { AreaCalculatorComponent: UseDelegate, ... } } delegate_components! { new AreaCalculatorComponents { Rectangle: RectangleArea, Circle: CircleArea, ... } } ``` The example above helps `MyApp` implement `CanCalculateArea` by delegating to the `Rectangle` provider, and `CanCalculateArea` to `CircleArea`, via the `UseDelegate` provider using `AreaCalculatorComponents` as the inner lookup table based on the `Shape` type. ## Cross-Context Dependencies When the main target of a trait is a generic parameter instead of a context, like: ```rust #[cgp_component(AreaOfShapeCalculator)] pub trait CanCalculateAreaOfShape { fn area(&self, shape: &Shape) -> f64; } ``` This allows multiple `Shape` contexts to share dependencies through a common `Context` type. For example, we can introduce a `Scalar` type that is shared by all shapes: ```rust #[cgp_type] pub trait HasScalarType { type Scalar: Float; } #[cgp_component(AreaOfShapeCalculator)] pub trait CanCalculateAreaOfShape: HasScalarType { fn area(&self, shape: &Shape) -> Self::Scalar; } ``` - This way, individual shape types like `Rectangle` and `Circle` do not need to implement `HasScalarType`, or worry about all shapes using the same `Scalar` type to interop with each others. - The common context type can also provide value-level dependency injection, such as: ```rust #[cgp_auto_getter] pub trait HasGlobalScaleFactor: HasScalarType { fn global_scale_factor(&self) -> Self::Scalar; } #[cgp_impl(new GloballyScaledArea)] impl AreaCalculator where Self: HasGlobalScaleFactor, InnerCalculator: AreaCalculator, { fn area(&self, shape: &Shape) -> f64 { InnerCalculator::area(self, shape) * self.global_scale_factor() } } ``` This way, a global scale factor can be stored in the common context, and not have to have the value replicated in all shape values. The common context can also provide lazy binding of provider implementations, so that each shape type may bind to different provider in different concrete contexts. For example: ```rust pub struct BaseApp; delegate_components! { BaseApp { ScaleFactorTypeProviderComponent: UseType, AreaOfShapeCalculatorComponent: UseDelegate, } } #[derive(HasField)] pub struct ScaledApp { pub global_scale_factor: f64, } delegate_components! { BaseApp { ScaleFactorTypeProviderComponent: UseType, AreaOfShapeCalculatorComponent: UseDelegate, Circle: GloballyScaledArea, }>, } } ``` In the above example, the `Rectangle` type would have an unscaled area implementation with `BaseApp`, but a globally scaled area implementation with `ScaledApp`. ## `UseContext` Provider - CGP defines a special `UseContext` provider that is automatically implemented for all CGP traits that are defined with macros like `#[cgp_component]`: ```rust struct UseContext; ``` - For example, the `UseContext` implementation generated for `CanCalculateArea` is as follows: ```rust #[cgp_impl(UseContext)] impl AreaOfShapeCalculator where Self: CanCalculateAreaOfShape, { fn area(&self, shape: &Shape) -> Self::Scalar { self.area(shape) } } ``` There is a duality between `UseContext` and the blanket implementation of consumer traits. Whereas the blanket implementation of the `CanCalculateArea` consumer trait uses a delegated provider that implements `AreaCalculator` to implement `CanCalculateArea`, the `UseContext` provider implements the `AreaCalculator` provider trait using `CanCalculateArea` implemented by the context. However, trying to delegate a consumer trait to `UseContext` would create a circular dependency, resulting in compile-time errors. ### `UseContext` as Default in Higher Order Providers A higher order provider may be configured to use `UseContext` as the default inner provider, so that the default provider wired in the context is used when no explicit provider is specified. For example, we can define an `IterSumArea` higher-order provider that uses `UseContext` as a default inner provider: ```rust pub struct IterSumArea(pub PhantomData); #[cgp_impl(IterSumArea)] impl AreaCalculator where Self: HasScalarType, for<'a> &'a Shape: IntoIterator InnerCalculator: AreaCalculator, { fn area(&self, shapes: &Shape) -> Self::Scalar { let mut total = Self::Scalar::default(); for shape in shapes.into_iter() { total += InnerCalculator::area(self, shape); } total } } ``` The struct definition of `IterSumArea` is defined with `UseContext` being a default generic parameter for `InnerCalculator`. This way, when no explicit provider is specified, `IterSumArea` would just use the wiring in the context to calculate the area for the inner shape. The inner provider can be overridden to enable static binding that does not require routing through the main context. This can be useful for simplifying the wiring on the main context, or for overridding the existing wiring in the main context. Note that the default `UseContext` provider is only applicable for higher order providers with explicit struct definitions that contain the default generic parameter. Otherwise, there is no default provider involved, and the inner provider must always be specified explicitly. ## Check Traits The CGP component wiring is lazy, i.e. when a `DelegateComponent` impl is defined, the type system doesn't check whether the corresponding traits are truly implemented by a context with all transitive dependencies satisfied. When a consumer trait is used with a context, but there are unsatisfied dependencies, the compiler will produce short error messages that are difficult to debug and identify the root cause. To ensure that a consumer is implemented by a context, we implement check traits to assert at compile time that the wiring is complete. For example, given: ```rust #[cgp_auto_getter] pub trait HasName { fn name(&self) -> &str; } #[cgp_component(Greeter)] pub trait CanGreet { fn greet(&self); } #[cgp_impl(new GreetHello)] impl Greeter where Self: HasName, { fn greet(&self) { println!("Hello, {}!", self.name()); } } #[derive(HasField)] pub struct Person { pub first_name: String, } delegate_components! { Person { GreeterComponent: GreetHello, } } ``` The `Person` struct above incorrectly contains a `first_name` field, instead of the `name` field expected by `GreetHello` via `HasName`. We can write a check trait to check whether `Person` implements `CanGreet` as follows: ```rust trait CanUsePerson: CanGreet {} impl CanUsePerson for Person {} ``` A check trait like `CanUsePerson` is a dummy trait that includes the dependencies that we want to check as its super trait. The check trait contains an empty body that can be trivially implemented if all the supertrait constraints are satisfied. We then implement the check trait for the context type that we want to check. If the type also implements all the supertraits, then the implementation is successful and the test passes. ## `CanUseComponent` Trait It is insufficient to use check traits alone in case when a check fails. This is because Rust would not produce sufficient details in the error message to help inform us on what dependency is missing. For example, the `CanUsePerson` check earlier only output a vague error that tells us `GreetHello: Greeter` is not implemented, without telling us why. We can use check traits together with `CanUseComponent` as their supertraits to force the Rust compiler to show more error details. The `CanUseComponent` trait is a check trait defined as follows: ```rust pub trait CanUseComponent {} impl CanUseComponent for Context where Context: DelegateComponent, Context::Delegate: IsProviderFor, {} ``` `CanUseComponent` for a CGP component is automatically implemented for a context, if a context delegates the component to a provider, and the provider implements the provider trait for that context. The check is done via `IsProviderFor`, to ensure that the compiler generates appropriate error messages when there is any unsatisfied constraint. Without `IsProviderFor`, Rust would conceal the indirect errors and only show that the provider trait is not implemented without providing further details. ## `check_components!` Macro Additionally, instead of defining the check traits manually, we can use `check_components!` to simplify the definition of the compile time tests. The `check_components!` macro generates code that checks the CGP wiring of components using `CanUseComponent`. The static check is written with `check_components!` as follow: ```rust check_components! { Person { GreeterComponent, } } ``` Behind the scenes, the macro desugars the code above to: ```rust trait __CheckPerson: CanUseComponent {} impl __CheckPerson for Person {} ``` The check trait `__CheckPerson` is defined as a local alias trait to check the implementation of `CanUseComponent` with the same parameters. The name of the check trait follows `__Check{Context}` format, where `Context` is the target context type being checked. For each `Component` listed in `check_components!`, an impl block for `__CheckPerson` is defined. The example implementation `__CheckPerson` is implemented only if: - `Person` implements `CanUseComponent`. - `Person`'s delegate for `GreeterComponent`, `GreetHello`, implements `IsProviderFor`. Recall that `#[cgp_impl]` or `#[cgp_provider]` generates the implementation of `GreetHello: IsProviderFor` with the same constraints required for `GreetHello` to implement `Greeter`. Since the `name` field is missing, the compiler reports the error that `HasField` is not implemented for `Person`. The root cause is often hidden among many other non-essential messages, and types such as `symbol!("name")` are expanded into their Greek alphabets form. ### Specifying check trait name The name of the generated check trait can be overridden using a `#[check_trait] attribute. For example: ```rust check_components! { #[check_trait(CanUsePerson)] Person { GreeterComponent, } } ``` would generate a check trait of the name `CanUsePerson` instead of `__CheckPerson`. This is mainly useful when there are multiple use of `check_components!` in the same module, which would result in name conflict for the default check trait name. ### Generic Parameters in `check_components!` `check_components!` can be used with CGP traits containing generic parameters. For example, given: ```rust #[cgp_component(AreaOfShapeCalculator)] pub trait CanCalculateAreaOfShape { fn area(&self, shape: &Shape) -> f64; } ``` and the following check: ```rust check_components! { MyApp { AreaOfShapeCalculatorComponent: Rectangle, } } ``` would be desugared to: ```rust trait __CheckMyApp: CanUseComponent { } impl __CheckMyApp for MyApp {} ``` which would check for the implementation of `MyApp: CanCalculateAreaOfShape`. ## Multiple Generic Parameters in `check_components!` When there are more than one generic parameters, they are grouped into a tuple and placed in `Params`. For example, given: ```rust #[cgp_component(AreaOfShapeCalculator)] pub trait CanCalculateAreaOfShape { fn area(&self, shape: &Shape) -> Scalar; } ``` and the following check: ```rust check_components! { MyApp { AreaOfShapeCalculatorComponent: (Rectangle, f64), } } ``` would be desugared to: ```rust trait __CheckMyApp: CanUseComponent { } impl __CheckMyApp for MyApp {} ``` which would check for the implementation of `MyApp: CanCalculateAreaOfShape`. ## Array Syntax in `check_components!` When we want to check the implementation of a CGP component with multiple generic parameters, we can use the array syntax to group them together. For example: ```rust check_components! { MyApp { AreaCalculatorComponent: [ Rectangle, Circle, ], } } ``` is the same as writing: ```rust check_components! { MyApp { AreaCalculatorComponent: Rectangle, AreaCalculatorComponent: Circle, } } ``` We can also group by the `Component` key instead of the generic `Param`. For example: ```rust check_components! { MyApp { [ AreaCalculatorComponent, RotatorComponent, ]: Rectangle, } } ``` We can also group by both `Component` and `Param`. For example: ```rust check_components! { MyApp { [ AreaCalculatorComponent, RotatorComponent, ]: [ Rectangle, Circle, ], } } ``` would be the same as writing: ```rust check_components! { MyApp { AreaCalculatorComponent: Rectangle, RotatorComponent: Rectangle, AreaCalculatorComponent: Circle, RotatorComponent: Circle, } } ``` ### `#[check_providers]` attribute The `check_components!` macro supports the use of `#[check_providers]` attribute to implement the check of component implementation on specific providers. For example: ```rust check_components! { #[check_trait(CheckScaledRectangleProviders)] #[check_providers( RectangleAreaCalculator, ScaledAreaCalculator, )] ScaledRectangle { AreaCalculatorComponent, } } ``` Would implement checks that both `RectangleAreaCalculator` and `ScaledAreaCalculator` implement `AreaCalculator`. The generated code for `#[check_providers]` is as follows: ```rust trait CheckScaledRectangleProviders<__Component__, __Params__: ?Sized>: IsProviderFor<__Component__, ScaledRectangle, __Params__> { } impl CheckScaledRectangleProviders for RectangleAreaCalculator {} impl CheckScaledRectangleProviders for ScaledAreaCalculator {} ``` Compared to non-provider checks, the check trait has `IsProviderFor` as its supertrait, and the impl blocks are implemented for the provider types instead of the context type. The provider checks are especially useful for the case of checking higher order providers, where each of the provider implementation can be checked separately. For example, if the missing dependency affects `RectangleAreaCalculator`, then the check above would show errors on both `RectangleAreaCalculator` and `ScaledAreaCalculator`. But if the missing dependency affects only `ScaledAreaCalculator`, then the check above would only show error on `ScaledAreaCalculator`, which can help narrow down the root cause. ## `delegate_and_check_components!` Macro The `delegate_and_check_components!` macro combines both the use of `delegate_components!` and `check_components!` into a single macro, so that every delegation is automatically checked without needing to write a separate `check_components!` block. For example, given the following: ```rust delegate_and_check_components! { ScaledRectangle { AreaCalculatorComponent: ScaledAreaCalculator, } } ``` is equivalent to writing: ```rust delegate_components! { ScaledRectangle { AreaCalculatorComponent: ScaledAreaCalculator, } } check_components! { #[check_trait(__CanUseScaledRectangle)] ScaledRectangle { AreaCalculatorComponent, } } ``` The default name of the check trait generated by `delegate_and_check_components!` is `__CanUse{Context}`. This is different from the default name of the check trait generated by `check_components!`, which is `__Check{Context}`, so that both macros can be called at most once in the same module without name conflict. The check trait name can also be overridden by using `#[check_trait]` attribute: ```rust delegate_and_check_components! { #[check_trait(TestScaledRectangle)] ScaledRectangle { AreaCalculatorComponent: ScaledAreaCalculator, } } ``` It is recommended to use `delegate_and_check_components!` over `delegate_components!` in the main context wiring, so that the wiring is always checked and any error can be caught as early as possible. On the other hand, `delegate_components!` alone can be still be used for building intermediary provider tables that group multiple providers together. `delegate_components!` may also be used in more complex cases, such as when complex higher order providers or generic parameters are involved. In those cases, checking the use on separate `check_components!` block may be more flexible. ### Specifying generic parameters with `#[check_params]` When delegating a CGP trait with generic parameters, a `#[check_params]` attribute is required to specify the generic parameters for the check. For example, given: ```rust #[cgp_component(AreaOfShapeCalculator)] pub trait CanCalculateAreaOfShape { fn area(&self, shape: &Shape) -> f64; } ``` The parameters would need to be specified, so that the checks can be done on the specified paramters: ```rust delegate_and_check_components! { MyApp { #[check_params( Rectangle, Circle, )] AreaOfShapeCalculatorComponent: UseDelegate, } } ``` Without `#[check_params]`, the generated check trait would check for the implementation of `CanUseComponent`, which would not be satisfied since the generic parameter for `CanCalculateAreaOfShape` is missing. ## Skipping checks with `#[skip_check]` We can also skip checks for specific components by using `#[skip_check]` attribute. For example: ```rust delegate_and_check_components! { ScaledRectangle { #[skip_check] AreaCalculatorComponent: ScaledAreaCalculator, } } ``` This is mainly useful for the case when the check for that component is specifically done separately. With `#[skip_check]`, one don't need to define another `delegate_components!` block just to wire components without checks. ## Modularity Hierarchy The [reference for modularity hierarchy](references/modularity-hierarchy.md) summarizes the modularity hierarchy of the use of CGP and vanilla Rust constructs, using the `Serialize` trait from `serde` as the base example. ## Debugging Techniques When compile errors are shown for a failed component wiring, try to identify the root cause by walking through the transitive dependencies shown within the error messages. When you suspect that a required CGP component is not implemented for a context, try include that component in `check_components!` so that the error can be narrowed down. Note that not all trait are necessarily CGP component. An unsatisfied dependency can also be just regular trait or blanket trait. These dependencies cannot be verified via `check_components!`. ## General Instructions for LLMs When interacting with the user, assume that the user only has basic Rust programming experience and is not familiar with any concept in CGP. Only provide further explanation for CGP concepts if the user asks for the specific explanation. Otherwise, assume that the user already understand the CGP concepts used in the code. When the user asks you to explain CGP concepts: - You should assume that the user is not familiar with advanced Rust concepts, including generics, traits, blanket implementations, and the coherence problem. - You should assume that the user is not familiar with functional programming and type-level programming concepts. So try to explain concepts like type-level tables, lists, or string, using more familiar OOP concepts, such as v-table lookup. - Use full sentences and explain CGP concepts in ways that are approachable and engaging to beginners. You should include additional explanation for advanced concepts such as generics when applicable. When the user ask to explain a specific wiring in `delegate_components!`, always lookup the definition of the components and providers to give more details in your explanation. For example, if the user asks you to explain the following code: ```rust delegate_components! { Person { GretterComponent: GreetHello, } } ``` you should look up for the definitions of `GreetComponent` and `GreetHello`. This includes finding out what are the names of the consumer trait and provider trait for `GreetComponent`, and the trait or impl body. When the user asks about the implementation details of a provider, always perform lookup on the original definition, and also the definition of all dependencies that the provider uses. For example, if the user asks you to explain the following: ```rust #[cgp_impl(GreetHello)] impl Greeter for Context where Context: HasName, { fn greet(&self) { println!("Hello, {}!", self.name()); } } ``` you should lookup for the definition of `Greeter` and `HasName`, and include the details of those definitions in your explanation. When the user asks about how a provider is implemented for a context, also perform lookup based on the specific wiring for the context, and find out what other providers that implementation is linked to. For example, if you need to explain `GreetHello`, and you find the following wiring: ```rust delegate_components! { Person { NameGetterComponent: UseField<"person_name">, GretterComponent: GreetHello, } } ``` then you should explain that for the `Person` context, since `NameGetterComponent` is wired to `UseField<"person_name">`, so when `println!("Hello, {}!", self.name())` is called from `GreetHello`, the value from `person_name` field will be returned from `self.name()`.