diff --git a/crates/core/cgp-error/src/traits/can_raise_error.rs b/crates/core/cgp-error/src/traits/can_raise_error.rs index ea91d7aa..4d177329 100644 --- a/crates/core/cgp-error/src/traits/can_raise_error.rs +++ b/crates/core/cgp-error/src/traits/can_raise_error.rs @@ -10,6 +10,7 @@ use crate::traits::has_error_type::HasErrorType; #[cgp_component(ErrorRaiser)] #[prefix(@cgp.core.error in DefaultNamespace)] #[derive_delegate(UseDelegate)] -pub trait CanRaiseError: HasErrorType { - fn raise_error(error: SourceError) -> Self::Error; +#[use_type(HasErrorType::Error)] +pub trait CanRaiseError { + fn raise_error(error: SourceError) -> Error; } diff --git a/crates/core/cgp-error/src/traits/can_wrap_error.rs b/crates/core/cgp-error/src/traits/can_wrap_error.rs index f32321a4..e75c5695 100644 --- a/crates/core/cgp-error/src/traits/can_wrap_error.rs +++ b/crates/core/cgp-error/src/traits/can_wrap_error.rs @@ -6,6 +6,7 @@ use crate::traits::HasErrorType; #[cgp_component(ErrorWrapper)] #[prefix(@cgp.core.error in DefaultNamespace)] #[derive_delegate(UseDelegate)] -pub trait CanWrapError: HasErrorType { - fn wrap_error(error: Self::Error, detail: Detail) -> Self::Error; +#[use_type(HasErrorType::Error)] +pub trait CanWrapError { + fn wrap_error(error: Error, detail: Detail) -> Error; } diff --git a/crates/extra/cgp-handler/src/components/handler.rs b/crates/extra/cgp-handler/src/components/handler.rs index 02ff9676..45d7d652 100644 --- a/crates/extra/cgp-handler/src/components/handler.rs +++ b/crates/extra/cgp-handler/src/components/handler.rs @@ -10,14 +10,11 @@ use crate::UseInputDelegate; #[prefix(@cgp.extra.handler in DefaultNamespace)] #[derive_delegate(UseDelegate)] #[derive_delegate(UseInputDelegate)] -pub trait CanHandle: HasErrorType { +#[use_type(HasErrorType::Error)] +pub trait CanHandle { type Output; - async fn handle( - &self, - _tag: PhantomData, - input: Input, - ) -> Result; + async fn handle(&self, _tag: PhantomData, input: Input) -> Result; } #[async_trait] @@ -25,12 +22,13 @@ pub trait CanHandle: HasErrorType { #[prefix(@cgp.extra.handler in DefaultNamespace)] #[derive_delegate(UseDelegate)] #[derive_delegate(UseInputDelegate)] -pub trait CanHandleRef: HasErrorType { +#[use_type(HasErrorType::Error)] +pub trait CanHandleRef { type Output; async fn handle_ref( &self, _tag: PhantomData, input: &Input, - ) -> Result; + ) -> Result; } diff --git a/crates/extra/cgp-handler/src/components/try_compute.rs b/crates/extra/cgp-handler/src/components/try_compute.rs index 4e0a8304..ef996759 100644 --- a/crates/extra/cgp-handler/src/components/try_compute.rs +++ b/crates/extra/cgp-handler/src/components/try_compute.rs @@ -9,26 +9,24 @@ use crate::UseInputDelegate; #[prefix(@cgp.extra.handler in DefaultNamespace)] #[derive_delegate(UseDelegate)] #[derive_delegate(UseInputDelegate)] -pub trait CanTryCompute: HasErrorType { +#[use_type(HasErrorType::Error)] +pub trait CanTryCompute { type Output; - fn try_compute( - &self, - _code: PhantomData, - input: Input, - ) -> Result; + fn try_compute(&self, _code: PhantomData, input: Input) -> Result; } #[cgp_component(TryComputerRef)] #[prefix(@cgp.extra.handler in DefaultNamespace)] #[derive_delegate(UseDelegate)] #[derive_delegate(UseInputDelegate)] -pub trait CanTryComputeRef: HasErrorType { +#[use_type(HasErrorType::Error)] +pub trait CanTryComputeRef { type Output; fn try_compute_ref( &self, _code: PhantomData, input: &Input, - ) -> Result; + ) -> Result; } diff --git a/crates/extra/cgp-run/src/lib.rs b/crates/extra/cgp-run/src/lib.rs index 6330faf4..ecc6832b 100644 --- a/crates/extra/cgp-run/src/lib.rs +++ b/crates/extra/cgp-run/src/lib.rs @@ -8,16 +8,15 @@ use cgp::prelude::*; #[cgp_component(Runner)] #[async_trait] #[derive_delegate(UseDelegate)] -pub trait CanRun: HasErrorType { - async fn run(&self, _code: PhantomData) -> Result<(), Self::Error>; +#[use_type(HasErrorType::Error)] +pub trait CanRun { + async fn run(&self, _code: PhantomData) -> Result<(), Error>; } #[cgp_component(SendRunner)] #[async_trait] #[derive_delegate(UseDelegate)] -pub trait CanSendRun: HasErrorType { - fn send_run( - &self, - _code: PhantomData, - ) -> impl Future> + Send; +#[use_type(HasErrorType::Error)] +pub trait CanSendRun { + fn send_run(&self, _code: PhantomData) -> impl Future> + Send; } diff --git a/crates/extra/cgp-runtime/src/traits/has_runtime.rs b/crates/extra/cgp-runtime/src/traits/has_runtime.rs index 35b8c537..fa59dc17 100644 --- a/crates/extra/cgp-runtime/src/traits/has_runtime.rs +++ b/crates/extra/cgp-runtime/src/traits/has_runtime.rs @@ -3,6 +3,7 @@ use cgp::prelude::*; use crate::HasRuntimeType; #[cgp_getter] -pub trait HasRuntime: HasRuntimeType { - fn runtime(&self) -> &Self::Runtime; +#[use_type(HasRuntimeType::Runtime)] +pub trait HasRuntime { + fn runtime(&self) -> &Runtime; } diff --git a/docs/concepts/README.md b/docs/concepts/README.md index b107c418..7629e843 100644 --- a/docs/concepts/README.md +++ b/docs/concepts/README.md @@ -12,6 +12,7 @@ The authoring rules for concept documents, including when a cross-cutting idea e - [Bypassing coherence](coherence.md) — what Rust's coherence rules forbid, and the incoherent-impl-plus-local-wiring strategy CGP uses to work around them. - [Modularity hierarchy](modularity-hierarchy.md) — the ladder from a single blanket impl to per-type-per-provider wiring, and how to pick the lowest rung a use case needs. +- [Modern idioms: a migration guide](modern-idioms.md) — the preferred higher-level forms for providers, dependencies, abstract types, and dispatch, mapped from the explicit forms they replace. - [Consumer and provider traits](consumer-and-provider-traits.md) — the trait duality at the heart of CGP and how it sidesteps coherence. - [Impl-side dependencies](impl-side-dependencies.md) — dependency injection through the `where` clause of blanket impls. - [Implicit arguments](implicit-arguments.md) — writing providers as ordinary functions whose arguments come from context fields. diff --git a/docs/concepts/abstract-types.md b/docs/concepts/abstract-types.md index 57eec597..4e96d076 100644 --- a/docs/concepts/abstract-types.md +++ b/docs/concepts/abstract-types.md @@ -55,8 +55,9 @@ pub trait HasScalarType { } #[cgp_component(AreaOfShapeCalculator)] -pub trait CanCalculateAreaOfShape: HasScalarType { - fn area(&self, shape: &Shape) -> Self::Scalar; +#[use_type(HasScalarType::Scalar)] +pub trait CanCalculateAreaOfShape { + fn area(&self, shape: &Shape) -> Scalar; } ``` diff --git a/docs/concepts/higher-order-providers.md b/docs/concepts/higher-order-providers.md index 0e7bb59e..5482a2b1 100644 --- a/docs/concepts/higher-order-providers.md +++ b/docs/concepts/higher-order-providers.md @@ -75,7 +75,7 @@ Here `Tag` is a type-level field name, used only as a `HasField` key, with no pr ## Related constructs -Higher-order providers are written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md) (or `#[cgp_fn]`), giving the inner provider as a generic parameter in the provider's `Self` position. The [`#[use_provider]`](../reference/attributes/use_provider.md) attribute is the idiomatic tool for them: it supplies the hidden `` on the inner bound, leaving the body to invoke the inner provider as the associated-function call the provider trait actually requires. [`UseContext`](../reference/providers/use_context.md) serves as the default inner provider when the higher-order provider is given an explicit struct with a defaulted parameter, letting it fall back to the context's own wiring. For dispatching to different inner providers based on a generic type rather than naming one statically, [`UseDelegate`](../reference/providers/use_delegate.md) pairs naturally with higher-order providers — a nested delegation table can map each shape to a different `ScaledArea<...>`. Each layer of a nested provider can be verified independently with the `#[check_providers]` form of [`check_components!`](../reference/macros/check_components.md), which is what makes higher-order wiring debuggable. +Higher-order providers are written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md) (or `#[cgp_fn]`), giving the inner provider as a generic parameter in the provider's `Self` position. The [`#[use_provider]`](../reference/attributes/use_provider.md) attribute is the idiomatic tool for them: it supplies the hidden `` on the inner bound, leaving the body to invoke the inner provider as the associated-function call the provider trait actually requires. [`UseContext`](../reference/providers/use_context.md) serves as the default inner provider when the higher-order provider is given an explicit struct with a defaulted parameter, letting it fall back to the context's own wiring. For dispatching to different inner providers based on a generic type rather than naming one statically, prefer the [`open` statement](../reference/macros/delegate_components.md) of `delegate_components!`, which maps each shape to a different `ScaledArea<...>` through the `RedirectLookup` impl every component carries; the legacy [`UseDelegate`](../reference/providers/use_delegate.md) nested table does the same job for older code. Each layer of a nested provider can be verified independently with the `#[check_providers]` form of [`check_components!`](../reference/macros/check_components.md), which is what makes higher-order wiring debuggable. ## Source diff --git a/docs/concepts/implicit-arguments.md b/docs/concepts/implicit-arguments.md index 35b56aa6..97490022 100644 --- a/docs/concepts/implicit-arguments.md +++ b/docs/concepts/implicit-arguments.md @@ -33,12 +33,12 @@ These same rules govern getter access in [`#[cgp_auto_getter]`](../reference/mac ## When to use which -Implicit arguments and getter traits both read context fields through `HasField`, and the choice between them is about where the value is needed. An implicit argument is the right tool when a value is consumed once, at the start of a method, as if it were a parameter — it keeps the field access local to the one function that uses it. A getter trait written with [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) is better when the same field is read across many methods, or part-way through a body rather than up front, because it exposes the field as a reusable `self.name()` accessor instead of re-declaring an implicit argument in every function. +An implicit argument is the default way to read a context field, and a getter trait is the exception reserved for publishing a reusable capability. Both read fields through `HasField` and share the same access rules, so the choice is not about mechanics but about what the value *is*. An implicit argument treats the value as a private input to one provider: it is bound as a local at the top of the method and used freely from there, which covers reading a field once, using it throughout a body, or declaring it on each of several methods that need it. This is the form to reach for whenever a provider simply needs a value from its context. -The two are complementary rather than competing, and a provider commonly uses both. Reach for an implicit argument to inject the inputs a single computation consumes, and for a getter trait to expose a field that several computations share; the underlying `HasField` machinery and access rules are identical, so mixing them carries no conceptual overhead. +A getter trait written with [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) is worth defining only when the value is a *published capability* rather than a private input — a named `self.name()` accessor that other providers depend on through `#[uses(HasName)]`, or a getter whose associated type is inferred from the field so the type stays abstract. Declaring such a trait promotes the field to part of the context's capability surface. When all a provider wants is to read a field for its own computation, an implicit argument says exactly that with less ceremony, so prefer it and treat the getter trait as the deliberate step of exposing a shared capability. The full wireable getter, [`#[cgp_getter]`](../reference/macros/cgp_getter.md), is a further step still, reserved for the advanced case of choosing the source field per context at wiring time. ## Related constructs The [`#[implicit]`](../reference/attributes/implicit.md) attribute is the construct itself, usable inside [`#[cgp_fn]`](../reference/macros/cgp_fn.md) and [`#[cgp_impl]`](../reference/macros/cgp_impl.md) — the former producing a single-implementation capability with no wiring, the latter a provider for a [`#[cgp_component]`](../reference/macros/cgp_component.md). All of them desugar implicit arguments into [`HasField`](../reference/traits/has_field.md) bounds, which a context supplies by deriving [`#[derive(HasField)]`](../reference/derives/derive_has_field.md). -For repeated or mid-body field access, [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) is the getter-trait counterpart that shares the same `.clone()`/`.as_str()` access rules. The whole model is value-level [impl-side dependencies](impl-side-dependencies.md): an implicit argument is a context dependency injected through a `where`-clause `HasField` bound and dressed as a function parameter. +For publishing a field as a shared, reusable accessor, [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) is the getter-trait counterpart that shares the same `.clone()`/`.as_str()` access rules — but a provider reading a field for its own use should prefer an implicit argument. The whole model is value-level [impl-side dependencies](impl-side-dependencies.md): an implicit argument is a context dependency injected through a `where`-clause `HasField` bound and dressed as a function parameter. diff --git a/docs/concepts/modern-idioms.md b/docs/concepts/modern-idioms.md new file mode 100644 index 00000000..d15e24ae --- /dev/null +++ b/docs/concepts/modern-idioms.md @@ -0,0 +1,211 @@ +# Modern idioms: a migration guide + +CGP offers a set of newer, higher-level idioms for writing components, providers, and wiring that read much closer to ordinary Rust — and this guide maps each older, more explicit form to the modern one you should prefer. + +The explicit forms came first. Early CGP exposed the machinery directly: a provider was an inside-out `impl` of a provider trait, dependencies were spelled as `where` clauses, abstract types were pulled from supertraits and written in fully-qualified `::Type` form, and per-type dispatch went through a `UseDelegate` table. Those forms still work and are exactly what the macros desugar to, so you will keep reading them in generated code, in expansion documentation, and in existing codebases. The newer idioms exist to lower the barrier to entry: they let a provider look like an ordinary trait `impl`, a dependency look like a `use` import, and an abstract type look like a plain generic, so that a reader who knows Rust but not CGP can follow the code. **Prefer the modern idioms in all new code, and reach for an explicit form only when a construct genuinely cannot express the case.** + +This guide is organized by the shift each idiom makes. The concepts it draws on are documented in full elsewhere: writing providers in [consumer and provider traits](consumer-and-provider-traits.md), dependency injection in [impl-side dependencies](impl-side-dependencies.md), field injection in [implicit arguments](implicit-arguments.md), abstract types in [abstract types](abstract-types.md), the provider-parameterized pattern in [higher-order providers](higher-order-providers.md), and per-type dispatch in [dispatching](dispatching.md) and [namespaces](namespaces.md). Each section below links to the reference document that owns the construct. + +## Write providers with `#[cgp_impl]`, not the raw provider forms + +A provider should be written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md), which keeps `self`, `Self`, and the consumer method signatures, rather than with the lower-level [`#[cgp_provider]`](../reference/macros/cgp_provider.md) or [`#[cgp_new_provider]`](../reference/macros/cgp_new_provider.md), which require the inside-out provider-trait shape. The lower forms move the context into an explicit leading type parameter and force the method to take `context: &Context` instead of `&self`; `#[cgp_impl]` restores the familiar shape and performs that rewrite for you. The legacy form: + +```rust +#[cgp_new_provider] +impl AreaCalculator for RectangleArea +where + Context: HasField, + Context: HasField, +{ + fn area(context: &Context) -> f64 { + *context.get_field(PhantomData::) + * *context.get_field(PhantomData::) + } +} +``` + +becomes, with the modern idiom and [`#[implicit]`](../reference/attributes/implicit.md) arguments: + +```rust +#[cgp_impl(new RectangleArea)] +impl AreaCalculator { + fn area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { + width * height + } +} +``` + +`#[cgp_impl]` desugars back to `#[cgp_provider]`/`#[cgp_new_provider]`, so the raw forms are still what the reference documents show in their Expansion sections and what you read in generated code. Write the raw form yourself only when you specifically need the inside-out shape — for instance, to state a bound the sugar cannot express. + +## Omit the context parameter + +Inside a `#[cgp_impl]` block, prefer the unqualified `impl AreaCalculator` and let the macro insert the context parameter, rather than naming it explicitly as `impl AreaCalculator for Context`. Omitting `for Context` is what makes the provider read like an ordinary trait `impl`; the macro supplies a reserved context parameter and treats `self`/`Self` as the context. Write the context out by hand: + +```rust +#[cgp_impl(new RectangleArea)] +impl AreaCalculator for Context +where + Context: HasDimensions, +{ + fn area(&self) -> f64 { + self.width() * self.height() + } +} +``` + +only when you must name it — to bound it with a lifetime or higher-ranked bound the sugar cannot spell, or to refer to it by a readable name. Otherwise write the shorter form and use `#[uses(...)]` for the bound: + +```rust +#[cgp_impl(new RectangleArea)] +#[uses(HasDimensions)] +impl AreaCalculator { + fn area(&self) -> f64 { + self.width() * self.height() + } +} +``` + +## Declare dependencies with `#[uses]` and `#[use_provider]` + +State a provider's impl-side dependencies with [`#[uses(...)]`](../reference/attributes/uses.md) and [`#[use_provider(...)]`](../reference/attributes/use_provider.md) rather than hand-written `where` clauses, so a dependency reads like a `use` import instead of a trait bound. A capability the body calls on the context is imported with `#[uses]`: writing `#[uses(CanCalculateArea)]` adds `Self: CanCalculateArea` to the generated impl. An inner provider a higher-order provider delegates to is declared with `#[use_provider]`: writing `#[use_provider(InnerCalculator: AreaCalculator)]` adds the bound `InnerCalculator: AreaCalculator`, filling in the `` argument that a provider trait inserts. The legacy `where` forms: + +```rust +#[cgp_impl(new ScaledArea)] +impl AreaCalculator for Context +where + Self: HasField, + InnerCalculator: AreaCalculator, +{ + fn area(&self) -> f64 { /* ... */ } +} +``` + +become: + +```rust +#[cgp_impl(new ScaledArea)] +#[use_provider(InnerCalculator: AreaCalculator)] +impl AreaCalculator { + fn area(&self, #[implicit] scale_factor: f64) -> f64 { /* ... */ } +} +``` + +`#[uses(...)]` accepts only the simple `Trait` form, so a bound with associated-type equality such as `Iterator` must still be written as an explicit `where` clause. Both attributes desugar to the same `where` predicates they replace. + +## Read context fields with implicit arguments, not getter traits + +Read a value from a context field with an [`#[implicit]`](../reference/attributes/implicit.md) argument — in a [`#[cgp_impl]`](../reference/macros/cgp_impl.md) provider method just as in a [`#[cgp_fn]`](../reference/macros/cgp_fn.md) — rather than declaring a getter trait with [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md). An implicit argument names both a local variable and the field it is read from, so the field access reads like an ordinary parameter and the `HasField` machinery stays out of sight — the same shift the provider idioms make, applied to values. This is the default way to pull a field into a provider, and it covers the great majority of field reads: a value used throughout a body is bound once at the top and used freely thereafter, and a value shared across several methods is simply declared as an implicit argument on each. The getter-trait version pairs a `#[cgp_auto_getter]` declaration with a `#[uses(...)]` import: + +```rust +#[cgp_auto_getter] +pub trait HasDimensions { + fn width(&self) -> &f64; + fn height(&self) -> &f64; +} + +#[cgp_impl(new RectangleArea)] +#[uses(HasDimensions)] +impl AreaCalculator { + fn area(&self) -> f64 { + self.width() * self.height() + } +} +``` + +collapses to a provider that reads the two fields directly: + +```rust +#[cgp_impl(new RectangleArea)] +impl AreaCalculator { + fn area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { + width * height + } +} +``` + +Reserve `#[cgp_auto_getter]` for when you genuinely want to publish a reusable getter *capability* rather than read a field for your own use — a named `self.name()` accessor that other providers depend on through `#[uses(HasName)]`, or a getter whose associated type is inferred from the field (`type Name; fn name(&self) -> &Self::Name;`). Both idioms desugar to the same `HasField` bounds and share the same access rules — `.clone()` for an owned value, `.as_str()` for a `&str` — so choosing between them is about whether the value is a private input or a published capability, not about mechanics. + +Avoid [`#[cgp_getter]`](../reference/macros/cgp_getter.md) in ordinary code. It builds a full wireable component so the source field name can be chosen at wiring time through a [`UseField`](../reference/providers/use_field.md) provider, and that flexibility is reserved for the advanced case where you want full control over the context implementation — deciding per context which field a getter reads from, or supplying the value by means other than a same-named field. For the common case of reading a field, an implicit argument (or, for a published accessor, `#[cgp_auto_getter]`) is the form to write. + +## Import abstract types with `#[use_type]` + +Bring an abstract type into a definition with [`#[use_type]`](../reference/attributes/use_type.md) and write it as a bare alias, rather than declaring the owning trait as a supertrait and qualifying every use as `Self::Type`. The attribute does both jobs at once: `#[use_type(HasScalarType::Scalar)]` adds the trait as a supertrait (on a `#[cgp_component]`) or a `where` bound (on a `#[cgp_impl]`/`#[cgp_fn]`), and rewrites each bare `Scalar` to `::Scalar`. This is the preferred form even for the built-in error type: the legacy component definition + +```rust +#[cgp_component(Loader)] +pub trait CanLoad: HasErrorType { + fn load(&self, path: &str) -> Result; +} +``` + +becomes + +```rust +#[cgp_component(Loader)] +#[use_type(HasErrorType::Error)] +pub trait CanLoad { + fn load(&self, path: &str) -> Result; +} +``` + +One rule bounds the rewrite: it fires only on the bare identifier of an *imported* type. A construct's own **local associated type always stays qualified as `Self::Assoc`** — a handler that declares `type Output` writes `Self::Output`, never a bare `Output`, because `Output` is the trait's own type rather than one imported from another trait. A mixed signature such as `Result` is therefore exactly right: the local `Self::Output` stays qualified while the imported foreign `Error` is written bare. When a capability supertrait has no associated type to import, add it with [`#[extend]`](../reference/attributes/extend.md) rather than `#[use_type]`, as the next section describes. + +## Add supertraits with `#[extend]`, not native `:` syntax + +Add a non-type capability supertrait to a [`#[cgp_component]`](../reference/macros/cgp_component.md) trait with [`#[extend(...)]`](../reference/attributes/extend.md), rather than writing the native `pub trait CanDoX: Supertrait` form. Both produce the same trait with the same supertrait, but the attribute reads as an import — a capability the trait re-exports — which matches how CGP actually uses supertraits: as declared dependencies, not as a base class. Native `:` supertrait syntax tends to read as inheritance to programmers coming from object-oriented languages, suggesting an is-a relationship to a parent that a CGP component does not have. `#[extend(...)]` avoids that misreading and pairs symmetrically with [`#[uses(...)]`](../reference/attributes/uses.md): `#[uses]` imports a capability for the implementation's private use, `#[extend]` re-exports one as part of the trait's public contract. The native form: + +```rust +#[cgp_component(Greeter)] +pub trait CanGreet: HasName { + fn greet(&self) -> String; +} +``` + +becomes: + +```rust +#[cgp_component(Greeter)] +#[extend(HasName)] +pub trait CanGreet { + fn greet(&self) -> String; +} +``` + +`#[extend]` is the tool for a supertrait that contributes only a *capability* — like `HasName` here, which `CanGreet` depends on but whose value it reads through the getter rather than naming an abstract type in the signature. When the supertrait is instead an **abstract-type component** whose associated type the signature does name, use [`#[use_type]`](../reference/attributes/use_type.md) instead, exactly as the previous section showed with `HasErrorType`: `#[use_type]` adds the supertrait *and* rewrites the bare type, which `#[extend]` does not, so it is the recommended form for abstract-type components. In [`#[cgp_fn]`](../reference/macros/cgp_fn.md), whose `where` clauses are impl-side dependencies, `#[extend]` is the only way to declare a supertrait at all. + +## Dispatch per type with `open` and namespaces, not `UseDelegate` + +Route a generic-parameter component to a different provider per type with the [`open` statement](../reference/macros/delegate_components.md) or a [namespace](namespaces.md), rather than the legacy [`UseDelegate`](../reference/providers/use_delegate.md) nested-table pattern. Both the `open` statement and namespaces dispatch through the `RedirectLookup` impl that every [`#[cgp_component]`](../reference/macros/cgp_component.md) already generates, so they store the per-type entries directly on the context and need no wrapper type. The legacy form nests a `UseDelegate` table: + +```rust +delegate_components! { + MyApp { + AreaCalculatorComponent: + UseDelegate, + } +} +``` + +while the modern form dispatches inline with `open`: + +```rust +delegate_components! { + MyApp { + open { AreaCalculatorComponent }; + + @AreaCalculatorComponent.Rectangle: RectangleArea, + @AreaCalculatorComponent.Circle: CircleArea, + } +} +``` + +Because `open` and namespaces ride `RedirectLookup`, **a new component you intend to dispatch this way does not need the [`#[derive_delegate(UseDelegate)]`](../reference/attributes/derive_delegate.md) attribute at all** — that attribute exists only to generate the `UseDelegate` provider the legacy nested-table form relies on. You will still see `#[derive_delegate]` on some CGP-shipped components, such as the error and handler families, which carry it so existing `UseDelegate`-based wiring keeps working; but code that dispatches only through `open` or a namespace can omit it. Prefer `open` for a self-contained context wiring its own components, and a [namespace](namespaces.md) when a reusable, inheritable dispatch table is worth sharing across contexts. + +## When the explicit forms are still right + +A handful of cases genuinely need an explicit form, and reaching for one there is not a regression. Keep an explicit `where` clause for a bound `#[uses]` cannot express — anything with associated-type equality. Name the context explicitly, as `impl Trait for Context`, when you must attach a lifetime or higher-ranked bound the sugar cannot carry. Reach for [`#[cgp_getter]`](../reference/macros/cgp_getter.md) when you specifically want full control over which field a getter reads from, chosen per context at wiring time. Write a raw provider-trait `impl` when you need the inside-out shape directly, for instance a provider whose `Self` is a concrete context rather than a generic one. And keep a local associated type qualified as `Self::Output` always — it is never a `#[use_type]` import. In every other case, the modern idiom is the one to write. + +Further reference: the per-construct mechanics live in the reference documents linked above; [modularity hierarchy](modularity-hierarchy.md) frames how much CGP a problem needs, and [consumer and provider traits](consumer-and-provider-traits.md) explains the duality the provider idioms rest on. diff --git a/docs/concepts/modular-error-handling.md b/docs/concepts/modular-error-handling.md index 9f7d3988..1070a83a 100644 --- a/docs/concepts/modular-error-handling.md +++ b/docs/concepts/modular-error-handling.md @@ -23,19 +23,21 @@ Centralizing the error on one trait is what lets errors compose across component ## Raising and wrapping as capabilities -Two further components give the abstract error its behavior, each parameterized so a context can handle many error shapes. [`CanRaiseError`](../reference/components/can_raise_error.md) converts a concrete source error into the context's abstract error, and its companion `CanWrapError` attaches a piece of detail to an existing one. Both supertrait `HasErrorType`, so the error they produce and enrich is the context's shared `Self::Error`: +Two further components give the abstract error its behavior, each parameterized so a context can handle many error shapes. [`CanRaiseError`](../reference/components/can_raise_error.md) converts a concrete source error into the context's abstract error, and its companion `CanWrapError` attaches a piece of detail to an existing one. Both import the abstract error with `#[use_type(HasErrorType::Error)]`, which adds the `HasErrorType` supertrait and lets each signature name the error as the bare `Error`, so the error they produce and enrich is the context's shared error type: ```rust #[cgp_component(ErrorRaiser)] #[derive_delegate(UseDelegate)] -pub trait CanRaiseError: HasErrorType { - fn raise_error(error: SourceError) -> Self::Error; +#[use_type(HasErrorType::Error)] +pub trait CanRaiseError { + fn raise_error(error: SourceError) -> Error; } #[cgp_component(ErrorWrapper)] #[derive_delegate(UseDelegate)] -pub trait CanWrapError: HasErrorType { - fn wrap_error(error: Self::Error, detail: Detail) -> Self::Error; +#[use_type(HasErrorType::Error)] +pub trait CanWrapError { + fn wrap_error(error: Error, detail: Detail) -> Error; } ``` @@ -93,8 +95,9 @@ Nothing about modular error handling is confined to the built-in components; an ```rust #[cgp_component(HttpErrorRaiser)] -pub trait CanRaiseHttpError: HasErrorType { - fn raise_http_error(_code: Code, detail: Detail) -> Self::Error; +#[use_type(HasErrorType::Error)] +pub trait CanRaiseHttpError { + fn raise_http_error(_code: Code, detail: Detail) -> Error; } ``` diff --git a/docs/concepts/send-bounds.md b/docs/concepts/send-bounds.md index a68bebcc..216d02e6 100644 --- a/docs/concepts/send-bounds.md +++ b/docs/concepts/send-bounds.md @@ -10,7 +10,8 @@ An async CGP method advertises a future whose auto-traits the caller cannot name #[cgp_component(ApiHandler)] #[async_trait] #[derive_delegate(UseDelegate)] -pub trait CanHandleApi: HasErrorType { +#[use_type(HasErrorType::Error)] +pub trait CanHandleApi { type Request; type Response; @@ -18,7 +19,7 @@ pub trait CanHandleApi: HasErrorType { &self, _api: PhantomData, request: Self::Request, - ) -> Result; + ) -> Result; } ``` diff --git a/docs/concepts/type-level-dsls.md b/docs/concepts/type-level-dsls.md index 70ff95ab..d118133d 100644 --- a/docs/concepts/type-level-dsls.md +++ b/docs/concepts/type-level-dsls.md @@ -29,14 +29,15 @@ The interpreter is a single CGP component whose `Code` type parameter is the pro #[cgp_component(Handler)] #[derive_delegate(UseDelegate)] #[derive_delegate(UseInputDelegate)] -pub trait CanHandle: HasErrorType { +#[use_type(HasErrorType::Error)] +pub trait CanHandle { type Output; async fn handle( &self, _tag: PhantomData, input: Input, - ) -> Result; + ) -> Result; } ``` @@ -44,20 +45,21 @@ Running a program is then one call to the consumer method, passing the program t ## Providers as interpreters -A provider interprets one fragment by pattern-matching on the `Code` parameter through its generic arguments. Written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md), an interpreter reads like an ordinary method body while the context stays generic, and its `where` clause states — as [impl-side dependencies](impl-side-dependencies.md) — everything the fragment needs from the context: +A provider interprets one fragment by pattern-matching on the `Code` parameter through its generic arguments. Written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md), an interpreter reads like an ordinary method body while the context stays generic, and it states everything the fragment needs from the context as [impl-side dependencies](impl-side-dependencies.md) — capability bounds through [`#[uses(...)]`](../reference/attributes/uses.md) and the remaining structural bounds in the `where` clause: ```rust #[cgp_impl(new HandleStreamChecksum)] -impl Handler, Input> for Context +#[uses(CanRaiseError)] +#[use_type(HasErrorType::Error)] +impl Handler, Input> where - Context: CanRaiseError, Input: Unpin + TryStream, Hasher: Digest, Input::Ok: AsRef<[u8]>, { type Output = GenericArray; - async fn handle(/* ... */) -> Result { + async fn handle(/* ... */) -> Result { /* fold the stream into a digest */ } } diff --git a/docs/examples/application-builder.md b/docs/examples/application-builder.md index bcd09502..f183c452 100644 --- a/docs/examples/application-builder.md +++ b/docs/examples/application-builder.md @@ -73,17 +73,16 @@ pub struct SqliteClient { } #[cgp_impl(new BuildSqliteClient)] -impl Handler -where - Self: HasSqliteOptions + CanRaiseError, -{ +#[uses(HasSqliteOptions, CanRaiseError)] +#[use_type(HasErrorType::Error)] +impl Handler { type Output = SqliteClient; async fn handle( &self, _code: PhantomData, _input: Input, - ) -> Result { + ) -> Result { let journal_mode = SqliteJournalMode::from_str(self.db_journal_mode()).map_err(Self::raise_error)?; let db_options = SqliteConnectOptions::from_str(self.db_options()) @@ -114,13 +113,12 @@ pub struct HttpClient { } #[cgp_impl(new BuildHttpClient)] -impl Handler -where - Self: HasHttpClientConfig + CanRaiseError, -{ +#[uses(HasHttpClientConfig, CanRaiseError)] +#[use_type(HasErrorType::Error)] +impl Handler { type Output = HttpClient; - async fn handle(&self, _code: PhantomData, _input: Input) -> Result { + async fn handle(&self, _code: PhantomData, _input: Input) -> Result { let http_client = Client::builder() .user_agent(self.http_user_agent()) .connect_timeout(Duration::from_secs(5)) @@ -147,13 +145,12 @@ pub struct OpenAiClient { } #[cgp_impl(new BuildOpenAiClient)] -impl Handler -where - Self: HasOpenAiConfig + HasErrorType, -{ +#[uses(HasOpenAiConfig)] +#[use_type(HasErrorType::Error)] +impl Handler { type Output = OpenAiClient; - async fn handle(&self, _code: PhantomData, _input: Input) -> Result { + async fn handle(&self, _code: PhantomData, _input: Input) -> Result { let open_ai_client = openai::Client::new(self.open_ai_key()); let open_ai_agent = open_ai_client .agent(self.open_ai_model()) @@ -165,7 +162,7 @@ where } ``` -Each provider states exactly what it needs from the context in its `where` clause as an [impl-side dependency](../concepts/impl-side-dependencies.md), and nothing more. A builder whose construction cannot fail — like `BuildOpenAiClient` — only requires [`HasErrorType`](../reference/components/has_error_type.md) so its output type aligns with the others, while a fallible one adds the `CanRaiseError` it needs. +Each provider states exactly what it needs from the context through [`#[uses(...)]`](../reference/attributes/uses.md) as an [impl-side dependency](../concepts/impl-side-dependencies.md), and nothing more. A builder whose construction cannot fail — like `BuildOpenAiClient` — only imports the abstract error type with [`#[use_type(HasErrorType::Error)]`](../reference/attributes/use_type.md) so its output type aligns with the others, while a fallible one also lists the `CanRaiseError` it needs. ## Merging the outputs into the application @@ -245,13 +242,12 @@ pub struct PostgresClient { } #[cgp_impl(new BuildPostgresClient)] -impl Handler -where - Self: HasPostgresUrl + CanRaiseError, -{ +#[uses(HasPostgresUrl, CanRaiseError)] +#[use_type(HasErrorType::Error)] +impl Handler { type Output = PostgresClient; - async fn handle(&self, _code: PhantomData, _input: Input) -> Result { + async fn handle(&self, _code: PhantomData, _input: Input) -> Result { let postgres_pool = PgPool::connect(self.postgres_url()).await.map_err(Self::raise_error)?; Ok(PostgresClient { postgres_pool }) } diff --git a/docs/examples/money-transfer-api.md b/docs/examples/money-transfer-api.md index 64991cde..b5830b6b 100644 --- a/docs/examples/money-transfer-api.md +++ b/docs/examples/money-transfer-api.md @@ -45,7 +45,8 @@ Every endpoint is one case of a single component that dispatches on a marker typ #[cgp_component(ApiHandler)] #[async_trait] #[derive_delegate(UseDelegate)] -pub trait CanHandleApi: HasErrorType { +#[use_type(HasErrorType::Error)] +pub trait CanHandleApi { type Request; type Response; @@ -53,7 +54,7 @@ pub trait CanHandleApi: HasErrorType { &self, _api: PhantomData, request: Self::Request, - ) -> Result; + ) -> Result; } pub struct TransferApi; @@ -69,22 +70,22 @@ Each endpoint is a provider for `ApiHandler` that depends on business capabiliti ```rust #[cgp_component(MoneyTransferrer)] #[async_trait] -pub trait CanTransferMoney: - HasUserIdType + HasCurrencyType + HasQuantityType + HasErrorType -{ +#[use_type(HasUserIdType::UserId, HasCurrencyType::Currency, HasQuantityType::Quantity, HasErrorType::Error)] +pub trait CanTransferMoney { async fn transfer_money( &self, - sender: &Self::UserId, - recipient: &Self::UserId, - currency: &Self::Currency, - quantity: &Self::Quantity, - ) -> Result<(), Self::Error>; + sender: &UserId, + recipient: &UserId, + currency: &Currency, + quantity: &Quantity, + ) -> Result<(), Error>; } #[cgp_impl(new HandleTransfer)] +#[uses(CanTransferMoney, CanRaiseHttpError)] +#[use_type(HasErrorType::Error)] impl ApiHandler where - Self: CanTransferMoney + CanRaiseHttpError, Request: HasLoggedInUser + HasTransferMoneyFields, { type Request = Request; @@ -94,7 +95,7 @@ where &self, _api: PhantomData, request: Request, - ) -> Result<(), Self::Error> { + ) -> Result<(), Error> { let sender = request.logged_in_user().as_ref().ok_or_else(|| { Self::raise_http_error(ErrUnauthorized, "you must first login".into()) })?; @@ -122,9 +123,9 @@ Cross-cutting concerns are handlers that wrap another handler, which makes them ```rust #[cgp_impl(new HandleFromRequest)] +#[use_type(HasErrorType::Error)] impl ApiHandler where - Self: HasErrorType, InHandler: ApiHandler, Request: Into, { @@ -135,7 +136,7 @@ where &self, api: PhantomData, request: Self::Request, - ) -> Result { + ) -> Result { InHandler::handle_api(self, api, request.into()).await } } @@ -145,12 +146,13 @@ where ```rust #[cgp_impl(new UseBasicAuth)] +#[uses(CanQueryUserHashedPassword, CanCheckPassword)] +#[use_type(HasUserIdType::UserId, HasErrorType::Error)] impl ApiHandler where - Self: CanQueryUserHashedPassword + CanCheckPassword, InHandler: ApiHandler, InHandler::Request: HasLoggedInUserMut + HasBasicAuthHeader, - Self::UserId: Clone, + UserId: Clone, { type Request = InHandler::Request; type Response = InHandler::Response; @@ -159,7 +161,7 @@ where &self, api: PhantomData, mut request: Self::Request, - ) -> Result { + ) -> Result { if request.logged_in_user().is_none() && let Some((user_id, password)) = request.basic_auth_header() { @@ -179,9 +181,9 @@ where ```rust #[cgp_impl(ResponseToJson)] +#[use_type(HasErrorType::Error)] impl ApiHandler where - Self: HasErrorType, InHandler: ApiHandler, { type Request = InHandler::Request; @@ -191,7 +193,7 @@ where &self, api: PhantomData, request: Self::Request, - ) -> Result { + ) -> Result { let response = InHandler::handle_api(self, api, request).await?; Ok(Json(response)) } @@ -206,29 +208,33 @@ The business capabilities are satisfied by a provider that reads its data from c ```rust #[cgp_auto_getter] -pub trait HasMockedUserBalances: HasUserIdType + HasCurrencyType + HasQuantityType { +#[use_type(HasUserIdType::UserId, HasCurrencyType::Currency, HasQuantityType::Quantity)] +pub trait HasMockedUserBalances { fn user_balances( &self, - ) -> &Arc>>; + ) -> &Arc>>; } #[cgp_impl(UseMockedApp)] +#[uses( + HasMockedUserBalances, + CanRaiseHttpError, + CanRaiseHttpError, +)] +#[use_type(HasUserIdType::UserId, HasCurrencyType::Currency, HasQuantityType::Quantity, HasErrorType::Error)] impl MoneyTransferrer where - Self: HasMockedUserBalances - + CanRaiseHttpError - + CanRaiseHttpError, - Self::Quantity: CheckedAdd + CheckedSub, - Self::UserId: Ord + Clone, - Self::Currency: Ord + Clone, + Quantity: CheckedAdd + CheckedSub, + UserId: Ord + Clone, + Currency: Ord + Clone, { async fn transfer_money( &self, - sender: &Self::UserId, - recipient: &Self::UserId, - currency: &Self::Currency, - quantity: &Self::Quantity, - ) -> Result<(), Self::Error> { + sender: &UserId, + recipient: &UserId, + currency: &Currency, + quantity: &Quantity, + ) -> Result<(), Error> { let mut balances = self.user_balances().lock().await; /* debit the sender, credit the recipient, raising on overflow or missing accounts */ Ok(()) diff --git a/docs/examples/shell-scripting-dsl.md b/docs/examples/shell-scripting-dsl.md index c87d7efa..992f5da6 100644 --- a/docs/examples/shell-scripting-dsl.md +++ b/docs/examples/shell-scripting-dsl.md @@ -66,14 +66,15 @@ Every step of a program is interpreted by one component: the [`Handler`](../refe #[cgp_component(Handler)] #[derive_delegate(UseDelegate)] #[derive_delegate(UseInputDelegate)] -pub trait CanHandle: HasErrorType { +#[use_type(HasErrorType::Error)] +pub trait CanHandle { type Output; async fn handle( &self, _tag: PhantomData, input: Input, - ) -> Result; + ) -> Result; } ``` @@ -91,15 +92,16 @@ This is the whole point of the design: how a program is *written* is completely ## Interpreting one syntax with a provider -An interpreter is a `Handler` provider that pattern-matches on a single syntax type through its `Code` parameter. Written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md), it reads like an ordinary method implementation while `Context` stays generic. This provider interprets a `Checksum` syntax by consuming a stream of bytes and producing their digest: +An interpreter is a `Handler` provider that pattern-matches on a single syntax type through its `Code` parameter. Written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md), it reads like an ordinary method implementation — `self` is the context — while the context stays generic. This provider interprets a `Checksum` syntax by consuming a stream of bytes and producing their digest: ```rust pub struct Checksum(pub PhantomData); #[cgp_impl(new HandleStreamChecksum)] -impl Handler, Input> for Context +#[uses(CanRaiseError)] +#[use_type(HasErrorType::Error)] +impl Handler, Input> where - Context: CanRaiseError, Input: Unpin + TryStream, Hasher: Digest, Input::Ok: AsRef<[u8]>, @@ -107,13 +109,13 @@ where type Output = GenericArray; async fn handle( - _context: &Context, + &self, _tag: PhantomData>, mut input: Input, - ) -> Result { + ) -> Result { let mut hasher = Hasher::new(); - while let Some(bytes) = input.try_next().await.map_err(Context::raise_error)? { + while let Some(bytes) = input.try_next().await.map_err(Self::raise_error)? { hasher.update(bytes); } @@ -122,7 +124,7 @@ where } ``` -Two things make this provider reusable across contexts. It matches `Handler, …>` for *any* `Hasher` that implements `Digest`, so the one provider covers every hash algorithm. And it never names a concrete error type: the `CanRaiseError` bound lets it convert a stream error into the context's own abstract error via [`CanRaiseError`](../reference/components/can_raise_error.md), so a context using `anyhow`, `eyre`, or a bespoke error type all reuse the same code. The `where` clause states everything the provider needs from the context as an [impl-side dependency](../concepts/impl-side-dependencies.md); a context that cannot meet it simply cannot wire this provider. +Two things make this provider reusable across contexts. It matches `Handler, …>` for *any* `Hasher` that implements `Digest`, so the one provider covers every hash algorithm. And it never names a concrete error type: the `CanRaiseError` bound lets it convert a stream error into the context's own abstract error via [`CanRaiseError`](../reference/components/can_raise_error.md), so a context using `anyhow`, `eyre`, or a bespoke error type all reuse the same code. The [`#[uses(...)]`](../reference/attributes/uses.md) import and the `where` clause together state everything the provider needs from the context as [impl-side dependencies](../concepts/impl-side-dependencies.md); a context that cannot meet them simply cannot wire this provider. ## Dispatching on the program @@ -203,18 +205,18 @@ A language extension adds new syntax and its interpreters without forking the co pub struct BytesToHex; #[cgp_impl(new HandleBytesToHex)] -impl Handler for Context +#[use_type(HasErrorType::Error)] +impl Handler where - Context: HasErrorType, Input: AsRef<[u8]>, { type Output = String; async fn handle( - _context: &Context, + &self, _tag: PhantomData, input: Input, - ) -> Result { + ) -> Result { Ok(hex::encode(input)) } } diff --git a/docs/reference/attributes/extend.md b/docs/reference/attributes/extend.md index 9e380858..5fec54e0 100644 --- a/docs/reference/attributes/extend.md +++ b/docs/reference/attributes/extend.md @@ -8,7 +8,7 @@ The distinction from [`#[uses]`](uses.md) is the whole point. Both attributes accept the same simplified trait-path syntax and both feel like imports, but they import into different places. `#[uses(...)]` adds a hidden `Self` bound to the impl only — a private dependency that callers never see. `#[extend(...)]` adds a supertrait to the trait — a public requirement that becomes part of the contract. The natural way to describe the pair is that `#[extend(...)]` is the `pub use` equivalent of `#[uses(...)]`: where `#[uses(...)]` imports a capability for the implementation's own use, `#[extend(...)]` re-exports it as part of what the trait guarantees. -This framing also explains when to reach for it. `#[extend(...)]` exists to make supertraits approachable for programmers who are not yet comfortable with Rust's supertrait syntax, by presenting them as imports. Authors who are comfortable writing `pub trait Foo: Bar` directly can do so in [`#[cgp_component]`](../macros/cgp_component.md); the choice there is stylistic. In `#[cgp_fn]`, where direct supertrait syntax is unavailable, `#[extend(...)]` is the mechanism. +This framing also explains which supertraits `#[extend(...)]` is for and why it is the preferred way to declare them. `#[extend(...)]` is the tool for a **non-type capability supertrait** — a trait like `HasName` or `CanCalculateArea` that a component depends on but whose associated types it does not name in its own signatures. In [`#[cgp_component]`](../macros/cgp_component.md), prefer `#[extend(HasName)]` over the native `pub trait CanGreet: HasName` form: the native `:` syntax reads as inheritance to programmers coming from object-oriented languages, suggesting an is-a relationship to a parent class that a CGP component does not have, whereas `#[extend(...)]` reads as importing a capability the trait re-exports — which is what a CGP supertrait actually is. When the supertrait is instead an **abstract-type component** whose associated type the signatures reference — such as [`HasErrorType`](../components/has_error_type.md) through its `Error` — prefer [`#[use_type]`](use_type.md), which adds the supertrait *and* rewrites the bare type; `#[use_type]` is the recommended form for abstract-type components. In [`#[cgp_fn]`](../macros/cgp_fn.md), where direct supertrait syntax is unavailable because the body's `where` clauses are impl-side dependencies, `#[extend(...)]` is the only mechanism for a plain capability supertrait. ## Syntax @@ -24,7 +24,7 @@ Each entry names a trait that becomes a supertrait of the generated trait, optio ## Expansion -`#[extend(...)]` adds each listed bound as a supertrait of the generated trait, and the same bound also appears in the impl's `where` clause so the implementation may rely on it. Starting from a `#[cgp_fn]` definition that depends on an abstract `Scalar` type: +`#[extend(...)]` adds each listed bound as a supertrait of the generated trait, and the same bound also appears in the impl's `where` clause so the implementation may rely on it. The example below uses the abstract-type trait `HasScalarType` to make the two-placement behavior visible in one signature; in production, an abstract-type supertrait like this is better declared with [`#[use_type]`](use_type.md), and `#[extend(...)]` is reserved for a non-type capability supertrait. Starting from a `#[cgp_fn]` definition that depends on an abstract `Scalar` type: ```rust pub trait HasScalarType { @@ -78,7 +78,7 @@ pub trait CanCalculateArea { } ``` -is the same as `pub trait CanCalculateArea: HasScalarType`. Here `#[extend(...)]` buys nothing the language does not already offer — it is available only so that the `use`/`pub use` pairing with `#[uses(...)]` reads consistently across both macros. +is the same as `pub trait CanCalculateArea: HasScalarType`. Although `#[extend(...)]` generates nothing the language cannot already spell here, it is still the preferred way to write the supertrait: it presents the bound as an import rather than as OOP-style inheritance, and it keeps the `use`/`pub use` pairing with `#[uses(...)]` reading consistently across both macros. ## Examples @@ -117,7 +117,7 @@ Because `HasScalarType` is a supertrait of `RectangleArea`, the abstract `Self:: ## Related constructs -`#[extend(...)]` is the `pub use` counterpart to [`#[uses]`](uses.md): the two share syntax but differ in placement, with `#[extend(...)]` adding public supertraits and `#[uses(...)]` adding hidden impl-side bounds. It is used in [`#[cgp_fn]`](../macros/cgp_fn.md), where it is the only way to declare supertraits, and in [`#[cgp_component]`](../macros/cgp_component.md), where it duplicates the native supertrait syntax for stylistic consistency. When the supertrait is an abstract-type trait whose associated type is referenced throughout the signature, prefer [`#[use_type]`](use_type.md), which adds the supertrait *and* rewrites bare type names into fully-qualified form. To add `where` clauses (not supertraits) to a `#[cgp_fn]` trait definition, use [`#[extend_where]`](extend_where.md). +`#[extend(...)]` is the `pub use` counterpart to [`#[uses]`](uses.md): the two share syntax but differ in placement, with `#[extend(...)]` adding public supertraits and `#[uses(...)]` adding hidden impl-side bounds. It is used in [`#[cgp_fn]`](../macros/cgp_fn.md), where it is the only way to declare supertraits, and in [`#[cgp_component]`](../macros/cgp_component.md), where it is the preferred alternative to native supertrait syntax because it reads as an import rather than as OOP-style inheritance. When the supertrait is an abstract-type trait whose associated type is referenced throughout the signature, prefer [`#[use_type]`](use_type.md), which adds the supertrait *and* rewrites bare type names into fully-qualified form. To add `where` clauses (not supertraits) to a `#[cgp_fn]` trait definition, use [`#[extend_where]`](extend_where.md). ## Source diff --git a/docs/reference/attributes/implicit.md b/docs/reference/attributes/implicit.md index 7c89d7ed..f18801e8 100644 --- a/docs/reference/attributes/implicit.md +++ b/docs/reference/attributes/implicit.md @@ -111,7 +111,7 @@ fn print_area(rect: &Rectangle) { ## Related constructs -`#[implicit]` is most often used inside [`#[cgp_fn]`](../macros/cgp_fn.md), which turns a function into a single-implementation capability, and inside [`#[cgp_impl]`](../macros/cgp_impl.md), which writes a provider for an existing component. It relies on [`#[derive(HasField)]`](../derives/derive_has_field.md) on the context to supply the field accessors that the generated bounds require. Its access rules — `.clone()` for owned values, `.as_str()` for `&str` — are shared with [`#[cgp_auto_getter]`](../macros/cgp_auto_getter.md), which is the better tool when the same fields are accessed across many methods or in the middle of a body. To bring in other CGP capabilities alongside implicit arguments, combine `#[implicit]` with [`#[uses]`](uses.md). +`#[implicit]` is most often used inside [`#[cgp_fn]`](../macros/cgp_fn.md), which turns a function into a single-implementation capability, and inside [`#[cgp_impl]`](../macros/cgp_impl.md), which writes a provider for an existing component. It relies on [`#[derive(HasField)]`](../derives/derive_has_field.md) on the context to supply the field accessors that the generated bounds require. Its access rules — `.clone()` for owned values, `.as_str()` for `&str` — are shared with [`#[cgp_auto_getter]`](../macros/cgp_auto_getter.md), which defines a reusable getter *capability* trait; prefer an implicit argument for reading a field as a provider's own input, and reserve `#[cgp_auto_getter]` for the case where the field should be published as a `self.name()` accessor other providers depend on. To bring in other CGP capabilities alongside implicit arguments, combine `#[implicit]` with [`#[uses]`](uses.md). ## Source diff --git a/docs/reference/attributes/use_type.md b/docs/reference/attributes/use_type.md index 18c52c5e..a7f485fe 100644 --- a/docs/reference/attributes/use_type.md +++ b/docs/reference/attributes/use_type.md @@ -71,6 +71,8 @@ where The substitution is purely textual at the type level: it matches single-segment type paths with no arguments whose identifier equals the imported name (or its alias), and replaces them with `::Scalar`. A bare `Scalar` anywhere — return type, implicit-argument annotation, or a `let` binding inside the body — is rewritten the same way, which is what makes nested uses work without the author writing any path. +Because the rewrite fires only on the bare identifier of an *imported* type, a construct's own **local associated types must always stay qualified as `Self::Assoc`** and are left untouched. A `#[cgp_component]` trait or a `#[cgp_impl]` provider that declares its own `type Output` refers to it as `Self::Output`, never as a bare `Output`, precisely because `Output` is the construct's own type rather than one imported from another trait — `#[use_type]` neither imports it nor rewrites it, and it should not be listed in a `#[use_type]` attribute. This is why a mixed signature such as `Result` is correct and idiomatic: the local `Self::Output` stays qualified while the imported foreign type `Error` (from `#[use_type(HasErrorType::Error)]`) is written bare. Attempting to write the local type bare would leave a `Output` identifier that resolves to nothing, since the substitution pass has no entry for it. + For `#[cgp_component]`, the trait is added as a supertrait rather than a `where` bound, and the rewrite touches the trait's own signatures. Starting from: ```rust diff --git a/docs/reference/components/can_raise_error.md b/docs/reference/components/can_raise_error.md index 0debe3a6..bcfaf0f1 100644 --- a/docs/reference/components/can_raise_error.md +++ b/docs/reference/components/can_raise_error.md @@ -10,18 +10,19 @@ ## Definition -Both traits supertrait [`HasErrorType`](has_error_type.md), so `Self::Error` refers to the context's shared abstract error type. Each is a `#[cgp_component]`, making it a full component with a generated provider trait. `CanRaiseError` converts a source error into the abstract error: +Both traits import the context's shared abstract error type with [`#[use_type(HasErrorType::Error)]`](../attributes/use_type.md), so a bare `Error` in either signature stands for the context's error rather than being written as `Self::Error`. Each is a `#[cgp_component]`, making it a full component with a generated provider trait. `CanRaiseError` converts a source error into the abstract error: ```rust #[cgp_component(ErrorRaiser)] #[prefix(@cgp.core.error in DefaultNamespace)] #[derive_delegate(UseDelegate)] -pub trait CanRaiseError: HasErrorType { - fn raise_error(error: SourceError) -> Self::Error; +#[use_type(HasErrorType::Error)] +pub trait CanRaiseError { + fn raise_error(error: SourceError) -> Error; } ``` -The `SourceError` parameter is the concrete error being raised, and `raise_error` is an associated function — it takes the source error by value and returns `Self::Error`, without needing a `self` receiver, because raising an error is a property of the context type rather than of any particular value. The `#[cgp_component(ErrorRaiser)]` attribute names the provider trait `ErrorRaiser`, and [`#[derive_delegate(UseDelegate)]`](../attributes/derive_delegate.md) wires `UseDelegate` so the raise behavior can be dispatched per source-error type through a delegation table — a context can handle each `SourceError` with a different provider. +The `SourceError` parameter is the concrete error being raised, and `raise_error` is an associated function — it takes the source error by value and returns the abstract `Error`, without needing a `self` receiver, because raising an error is a property of the context type rather than of any particular value. `#[use_type(HasErrorType::Error)]` adds `HasErrorType` as a supertrait and rewrites the bare `Error` to `::Error`. The `#[cgp_component(ErrorRaiser)]` attribute names the provider trait `ErrorRaiser`, and [`#[derive_delegate(UseDelegate)]`](../attributes/derive_delegate.md) wires `UseDelegate` so the raise behavior can be dispatched per source-error type through a delegation table — a context can handle each `SourceError` with a different provider. `CanWrapError` has the same shape but takes an existing error plus a detail: @@ -29,12 +30,13 @@ The `SourceError` parameter is the concrete error being raised, and `raise_error #[cgp_component(ErrorWrapper)] #[prefix(@cgp.core.error in DefaultNamespace)] #[derive_delegate(UseDelegate)] -pub trait CanWrapError: HasErrorType { - fn wrap_error(error: Self::Error, detail: Detail) -> Self::Error; +#[use_type(HasErrorType::Error)] +pub trait CanWrapError { + fn wrap_error(error: Error, detail: Detail) -> Error; } ``` -Here `wrap_error` takes the context's current `Self::Error` and a `Detail` value and returns a new `Self::Error` with the detail folded in. Its provider trait is `ErrorWrapper`, and it delegates per `Detail` type, so wrapping a string message and wrapping a structured detail can be handled by different providers. +Here `wrap_error` takes the context's current `Error` and a `Detail` value and returns a new `Error` with the detail folded in. Its provider trait is `ErrorWrapper`, and it delegates per `Detail` type, so wrapping a string message and wrapping a structured detail can be handled by different providers. ## Behavior @@ -50,19 +52,19 @@ A provider raises a concrete error into the abstract one and wraps a message ont use cgp::prelude::*; #[cgp_component(Loader)] -pub trait CanLoad: HasErrorType { - fn load(&self, path: &str) -> Result; +#[use_type(HasErrorType::Error)] +pub trait CanLoad { + fn load(&self, path: &str) -> Result; } #[cgp_impl(new LoadOrFail)] -impl Loader for Context -where - Context: CanRaiseError + CanWrapError, -{ - fn load(&self, path: &str) -> Result { +#[uses(CanRaiseError, CanWrapError)] +#[use_type(HasErrorType::Error)] +impl Loader { + fn load(&self, path: &str) -> Result { if path.is_empty() { - let err = Context::raise_error("empty path".to_owned()); - return Err(Context::wrap_error(err, format!("while loading {path}"))); + let err = Self::raise_error("empty path".to_owned()); + return Err(Self::wrap_error(err, format!("while loading {path}"))); } Ok(format!("contents of {path}")) } diff --git a/docs/reference/components/handler.md b/docs/reference/components/handler.md index 2adc0f6e..93b06b75 100644 --- a/docs/reference/components/handler.md +++ b/docs/reference/components/handler.md @@ -10,27 +10,28 @@ This generality is why the family promotes toward `Handler` rather than away fro ## Definition -`Handler` is a CGP component defined with `#[cgp_component]` under `#[async_trait]`, and its consumer trait `CanHandle` supertraits `HasErrorType` so its method can return the context's abstract error: +`Handler` is a CGP component defined with `#[cgp_component]` under `#[async_trait]`, and it imports the context's abstract error type through [`#[use_type(HasErrorType::Error)]`](../attributes/use_type.md) so its method can return that error by the bare name `Error`: ```rust #[async_trait] #[cgp_component(Handler)] #[derive_delegate(UseDelegate)] #[derive_delegate(UseInputDelegate)] -pub trait CanHandle: HasErrorType { +#[use_type(HasErrorType::Error)] +pub trait CanHandle { type Output; async fn handle( &self, _tag: PhantomData, input: Input, - ) -> Result; + ) -> Result; } ``` -The consumer trait `CanHandle` combines the async and fallible refinements of the base signature. Its `handle` method is declared `async` and returns `Result`, so it is the async counterpart of `CanTryCompute` and the fallible counterpart of `CanComputeAsync`. The `#[async_trait]` attribute rewrites the `async fn` into a method returning `impl Future>`, avoiding any boxed future. The component is wired through the generated `HandlerComponent` marker, its provider trait is `Handler` with the context moved into an explicit first parameter, and the two `#[derive_delegate(...)]` attributes generate dispatching providers keyed on `Code` and on `Input`. The `HasErrorType` supertrait supplies the `Self::Error` named in the result. +The consumer trait `CanHandle` combines the async and fallible refinements of the base signature. Its `handle` method is declared `async` and returns `Result`, so it is the async counterpart of `CanTryCompute` and the fallible counterpart of `CanComputeAsync`. `#[use_type(HasErrorType::Error)]` adds `HasErrorType` as a supertrait and rewrites the bare `Error` to `::Error`, which is why the definition never spells `HasErrorType` or `Self::Error` by hand; the local associated type `Output` stays qualified as `Self::Output`, because it is the trait's own type rather than an imported one. The `#[async_trait]` attribute then rewrites the `async fn` into a method returning `impl Future>`, avoiding any boxed future. The component is wired through the generated `HandlerComponent` marker, its provider trait is `Handler` with the context moved into an explicit first parameter, and the two `#[derive_delegate(...)]` attributes generate dispatching providers keyed on `Code` and on `Input`. -The by-reference sibling `HandlerRef` is identical except that it borrows its input. Its consumer trait `CanHandleRef` also supertraits `HasErrorType` and declares `async fn handle_ref(&self, _tag: PhantomData, input: &Input) -> Result`, taking `&Input` where `CanHandle` takes `Input`. +The by-reference sibling `HandlerRef` is identical except that it borrows its input. Its consumer trait `CanHandleRef` also imports the error type with `#[use_type(HasErrorType::Error)]` and declares `async fn handle_ref(&self, _tag: PhantomData, input: &Input) -> Result`, taking `&Input` where `CanHandle` takes `Input`. ## Implementations diff --git a/docs/reference/components/has_error_type.md b/docs/reference/components/has_error_type.md index cc65dbcb..043404c1 100644 --- a/docs/reference/components/has_error_type.md +++ b/docs/reference/components/has_error_type.md @@ -40,8 +40,9 @@ A context declares its abstract error and generic code returns it without naming use cgp::prelude::*; #[cgp_component(Validator)] -pub trait CanValidate: HasErrorType { - fn validate(&self) -> Result<(), Self::Error>; +#[use_type(HasErrorType::Error)] +pub trait CanValidate { + fn validate(&self) -> Result<(), Error>; } pub struct App; @@ -53,7 +54,7 @@ delegate_components! { } ``` -Here `CanValidate` supertraits `HasErrorType`, so its `Result<(), Self::Error>` refers to the context's shared abstract error. `App` wires its error-type component to `UseType`, fixing `Self::Error` to `String` (which satisfies the `Debug` bound). The same abstract error can equally be implemented directly: +Here [`#[use_type(HasErrorType::Error)]`](../attributes/use_type.md) adds `HasErrorType` as a supertrait of `CanValidate` and rewrites the bare `Error` to `::Error`, so `validate` returns the context's shared abstract error without writing `Self::Error`. `App` wires its error-type component to `UseType`, fixing that error to `String` (which satisfies the `Debug` bound). The same abstract error can equally be implemented directly: ```rust impl HasErrorType for App { diff --git a/docs/reference/components/has_runtime.md b/docs/reference/components/has_runtime.md index cffceca1..cada4a04 100644 --- a/docs/reference/components/has_runtime.md +++ b/docs/reference/components/has_runtime.md @@ -25,22 +25,23 @@ pub type RuntimeOf = ::Runtime; Because the trait carries no provider-name argument, `#[cgp_type]` derives the provider name from the associated type: `Runtime` yields the provider trait `RuntimeTypeProvider` and the component marker `RuntimeTypeProviderComponent`. The `Runtime` associated type carries no bound, so any concrete type may be plugged in. The `RuntimeOf` alias is the convenient spelling of the resolved runtime type, used wherever writing `::Runtime` in full would be noise. -`HasRuntime` is a getter component defined with [`#[cgp_getter]`](../macros/cgp_getter.md), and it supertraits `HasRuntimeType` so the runtime type is available as the getter's return type: +`HasRuntime` is a getter component defined with [`#[cgp_getter]`](../macros/cgp_getter.md), and it imports the runtime type with [`#[use_type(HasRuntimeType::Runtime)]`](../attributes/use_type.md) so the runtime type is available as the getter's return type under the bare name `Runtime`: ```rust #[cgp_getter] -pub trait HasRuntime: HasRuntimeType { - fn runtime(&self) -> &Self::Runtime; +#[use_type(HasRuntimeType::Runtime)] +pub trait HasRuntime { + fn runtime(&self) -> &Runtime; } ``` -`#[cgp_getter]` derives the provider name from the trait name by stripping the `Has` prefix and appending `Getter`, so `HasRuntime` yields the provider trait `RuntimeGetter` and the component marker `RuntimeGetterComponent`. The `runtime` method borrows the runtime value out of a borrow of the context, returning `&Self::Runtime` — the abstract type supplied by `HasRuntimeType`. +`#[cgp_getter]` derives the provider name from the trait name by stripping the `Has` prefix and appending `Getter`, so `HasRuntime` yields the provider trait `RuntimeGetter` and the component marker `RuntimeGetterComponent`. `#[use_type(HasRuntimeType::Runtime)]` adds `HasRuntimeType` as a supertrait and rewrites the bare `Runtime` to `::Runtime`, so the `runtime` method borrows the runtime value out of a borrow of the context, returning `&Runtime` — the abstract type supplied by `HasRuntimeType` — without writing `Self::Runtime` by hand. ## Behavior `HasRuntimeType` behaves like any `#[cgp_type]` abstract-type component, so a context supplies its runtime *type* either by implementing `HasRuntimeType` directly or — far more commonly — by wiring `RuntimeTypeProviderComponent` to [`UseType`](../providers/use_type.md) in `delegate_components!`. Wiring `RuntimeTypeProviderComponent: UseType` makes the context resolve `HasRuntimeType` with `Runtime = TokioRuntime`, and from then on `RuntimeOf` is `TokioRuntime`. The full set of generated constructs — the consumer and provider blanket impls, the `UseContext` and `RedirectLookup` provider impls, and the `UseType`/`WithProvider` impls that make `UseType` resolve the type — is exactly the `#[cgp_type]` expansion described in [`#[cgp_type]`](../macros/cgp_type.md). -`HasRuntime` behaves like any `#[cgp_getter]` getter component, so a context supplies its runtime *value* either by implementing `HasRuntime` directly or by wiring `RuntimeGetterComponent` to a [`UseField`](../providers/use_field.md) provider that names the field holding the runtime. Because `#[cgp_getter]` generates a `UseField` provider impl, a context that stores its runtime in a `runtime` field wires `RuntimeGetterComponent: UseField`, and the getter reads that field. The supertrait bound on `HasRuntimeType` means a context cannot satisfy `HasRuntime` without also having declared its runtime type, which keeps `&Self::Runtime` well-defined. +`HasRuntime` behaves like any `#[cgp_getter]` getter component, so a context supplies its runtime *value* either by implementing `HasRuntime` directly or by wiring `RuntimeGetterComponent` to a [`UseField`](../providers/use_field.md) provider that names the field holding the runtime. Because `#[cgp_getter]` generates a `UseField` provider impl, a context that stores its runtime in a `runtime` field wires `RuntimeGetterComponent: UseField`, and the getter reads that field. The `HasRuntimeType` supertrait that `#[use_type]` adds means a context cannot satisfy `HasRuntime` without also having declared its runtime type, which keeps the returned `&Runtime` well-defined. Together the two components let a context fully describe its runtime: one wiring entry fixes the abstract type, another fixes where the value lives. Context-generic providers then write `where Self: HasRuntime` and call `self.runtime()` to obtain a `&RuntimeOf`, never naming the concrete runtime. This is what makes the same task-running and effect-performing code reusable across a Tokio context, a mock context, and a test context with no changes beyond the wiring. diff --git a/docs/reference/components/runner.md b/docs/reference/components/runner.md index 2c2d27a2..0ea8bc1f 100644 --- a/docs/reference/components/runner.md +++ b/docs/reference/components/runner.md @@ -12,30 +12,32 @@ The family is the execution layer that ties together the rest of a CGP applicati ## Definition -Both components are declared in `cgp-run`, each with [`#[cgp_component]`](../macros/cgp_component.md), `#[async_trait]`, and a [`#[derive_delegate(UseDelegate)]`](../attributes/derive_delegate.md) attribute, and each supertraiting [`HasErrorType`](has_error_type.md) so the task can fail with the context's abstract error: +Both components are declared in `cgp-run`, each with [`#[cgp_component]`](../macros/cgp_component.md), `#[async_trait]`, a [`#[derive_delegate(UseDelegate)]`](../attributes/derive_delegate.md) attribute, and [`#[use_type(HasErrorType::Error)]`](../attributes/use_type.md) so the task can fail with the context's abstract error, written as the bare `Error`: ```rust #[cgp_component(Runner)] #[async_trait] #[derive_delegate(UseDelegate)] -pub trait CanRun: HasErrorType { - async fn run(&self, _code: PhantomData) -> Result<(), Self::Error>; +#[use_type(HasErrorType::Error)] +pub trait CanRun { + async fn run(&self, _code: PhantomData) -> Result<(), Error>; } #[cgp_component(SendRunner)] #[async_trait] #[derive_delegate(UseDelegate)] -pub trait CanSendRun: HasErrorType { +#[use_type(HasErrorType::Error)] +pub trait CanSendRun { fn send_run( &self, _code: PhantomData, - ) -> impl Future> + Send; + ) -> impl Future> + Send; } ``` -The `Code` parameter is the type-level name of the task to run; it is passed only as `PhantomData`, so it carries no data and exists purely to select an implementation. `#[cgp_component(Runner)]` names the provider trait `Runner` and the component marker `RunnerComponent`; `#[cgp_component(SendRunner)]` names them `SendRunner` and `SendRunnerComponent`. The [`#[derive_delegate(UseDelegate)]`](../attributes/derive_delegate.md) attribute generates a `UseDelegate` provider that dispatches on `Code`, so a context can route different `Code` tags to different runner providers through an inner delegation table. +The `Code` parameter is the type-level name of the task to run; it is passed only as `PhantomData`, so it carries no data and exists purely to select an implementation. `#[use_type(HasErrorType::Error)]` adds `HasErrorType` as a supertrait and rewrites the bare `Error` to `::Error`, so neither trait spells `HasErrorType` or `Self::Error` by hand. `#[cgp_component(Runner)]` names the provider trait `Runner` and the component marker `RunnerComponent`; `#[cgp_component(SendRunner)]` names them `SendRunner` and `SendRunnerComponent`. The [`#[derive_delegate(UseDelegate)]`](../attributes/derive_delegate.md) attribute generates a `UseDelegate` provider that dispatches on `Code`, so a context can route different `Code` tags to different runner providers through an inner delegation table. -The two traits differ only in how they shape the asynchronous return. `CanRun::run` is an ordinary `async fn` returning `Result<(), Self::Error>`, with no `Send` requirement on the future. `CanSendRun::send_run` instead returns an explicit `impl Future> + Send`, so callers may move the future across threads. Both produce `()` on success — the task's effect is observable elsewhere, not returned. +The two traits differ only in how they shape the asynchronous return. `CanRun::run` is an ordinary `async fn` returning `Result<(), Error>`, with no `Send` requirement on the future. `CanSendRun::send_run` instead returns an explicit `impl Future> + Send`, so callers may move the future across threads. Both produce `()` on success — the task's effect is observable elsewhere, not returned. ## Behavior @@ -75,13 +77,14 @@ Because this impl names the concrete `App` and `ActionA`, the future produced by A complete flow defines tasks, wires runners, proxies the `Send` variant, and spawns one task from inside another. A spawning provider requires the context to be `CanSendRun` and hands a `Send` future to a spawner: ```rust -#[cgp_new_provider(RunnerComponent)] -impl Runner for SpawnAndRun +#[cgp_impl(new SpawnAndRun: RunnerComponent)] +#[use_type(HasErrorType::Error)] +impl Runner where - Context: 'static + Send + Clone + CanSendRun, + Self: 'static + Send + Clone + CanSendRun, { - async fn run(context: &Context, _code: PhantomData) -> Result<(), Context::Error> { - let context = context.clone(); + async fn run(&self, _code: PhantomData) -> Result<(), Error> { + let context = self.clone(); spawn(async move { let _ = context.send_run(PhantomData).await; diff --git a/docs/reference/components/try_computer.md b/docs/reference/components/try_computer.md index a1f91fb8..82a33839 100644 --- a/docs/reference/components/try_computer.md +++ b/docs/reference/components/try_computer.md @@ -10,26 +10,27 @@ Returning the *context's* abstract error rather than a concrete one is what keep ## Definition -`TryComputer` is a CGP component defined with `#[cgp_component]`, and its consumer trait `CanTryCompute` supertraits `HasErrorType` so that its method can return the context's abstract error: +`TryComputer` is a CGP component defined with `#[cgp_component]`, and it imports the context's abstract error type through [`#[use_type(HasErrorType::Error)]`](../attributes/use_type.md) so that its method can return that error by the bare name `Error`: ```rust #[cgp_component(TryComputer)] #[derive_delegate(UseDelegate)] #[derive_delegate(UseInputDelegate)] -pub trait CanTryCompute: HasErrorType { +#[use_type(HasErrorType::Error)] +pub trait CanTryCompute { type Output; fn try_compute( &self, _code: PhantomData, input: Input, - ) -> Result; + ) -> Result; } ``` -The consumer trait `CanTryCompute` mirrors `CanCompute` but for the fallible case. Its `try_compute` method takes `&self`, a `PhantomData` naming the computation, and the `Input` by value, returning `Result` — the associated `Output` on success and the context's abstract `Error` (supplied by the `HasErrorType` supertrait) on failure. The component is wired through the generated `TryComputerComponent` marker, and the macro generates the provider trait `TryComputer` with the context moved into an explicit first parameter. The two `#[derive_delegate(...)]` attributes generate dispatching providers keyed on `Code` and on `Input`. +The consumer trait `CanTryCompute` mirrors `CanCompute` but for the fallible case. Its `try_compute` method takes `&self`, a `PhantomData` naming the computation, and the `Input` by value, returning `Result` — the associated `Output` on success and the context's abstract error on failure. `#[use_type(HasErrorType::Error)]` adds `HasErrorType` as a supertrait and rewrites the bare `Error` to `::Error`, so the definition writes neither `HasErrorType` nor `Self::Error` by hand; the local associated type `Output` stays qualified as `Self::Output`, since it is the trait's own type. The component is wired through the generated `TryComputerComponent` marker, and the macro generates the provider trait `TryComputer` with the context moved into an explicit first parameter. The two `#[derive_delegate(...)]` attributes generate dispatching providers keyed on `Code` and on `Input`. -The by-reference sibling `TryComputerRef` is identical except that it borrows its input. Its consumer trait `CanTryComputeRef` also supertraits `HasErrorType` and declares `fn try_compute_ref(&self, _code: PhantomData, input: &Input) -> Result`, taking `&Input` where `CanTryCompute` takes `Input`. Both components are synchronous; their async-and-fallible counterpart is `Handler`. +The by-reference sibling `TryComputerRef` is identical except that it borrows its input. Its consumer trait `CanTryComputeRef` also imports the error type with `#[use_type(HasErrorType::Error)]` and declares `fn try_compute_ref(&self, _code: PhantomData, input: &Input) -> Result`, taking `&Input` where `CanTryCompute` takes `Input`. Both components are synchronous; their async-and-fallible counterpart is `Handler`. ## Implementations @@ -63,19 +64,18 @@ use core::marker::PhantomData; use cgp::prelude::*; use cgp::extra::handler::{CanTryCompute, TryComputer, TryComputerComponent}; -#[cgp_new_provider] -impl TryComputer for ParseU64 -where - Context: CanRaiseError, -{ +#[cgp_impl(new ParseU64)] +#[uses(CanRaiseError)] +#[use_type(HasErrorType::Error)] +impl TryComputer { type Output = u64; fn try_compute( - context: &Context, + &self, _code: PhantomData, input: String, - ) -> Result { - input.parse().map_err(|e| Context::raise_error(e)) + ) -> Result { + input.parse().map_err(|e| Self::raise_error(e)) } } @@ -86,7 +86,7 @@ delegate_components! { } ``` -The provider `ParseU64` returns `Result`, converting the concrete `ParseIntError` into the context's abstract error with `CanRaiseError`. Because the consumer trait supertraits `HasErrorType`, the context must have an error type wired before it can call `try_compute` — `App` would delegate its `ErrorTypeProviderComponent` (and an error raiser) as well as `TryComputerComponent`. In everyday use the [`#[cgp_computer]`](../macros/cgp_computer.md) macro generates this kind of provider from a function returning `Result`, and wires the promotion table so the same function also answers `CanCompute` (returning the `Result` as its output), `CanHandle`, and the `Ref` variants. +The provider `ParseU64` returns its `u64` output or the context's abstract error, converting the concrete `ParseIntError` into that error with `CanRaiseError`. Because the consumer trait supertraits `HasErrorType`, the context must have an error type wired before it can call `try_compute` — `App` would delegate its `ErrorTypeProviderComponent` (and an error raiser) as well as `TryComputerComponent`. In everyday use the [`#[cgp_computer]`](../macros/cgp_computer.md) macro generates this kind of provider from a function returning `Result`, and wires the promotion table so the same function also answers `CanCompute` (returning the `Result` as its output), `CanHandle`, and the `Ref` variants. ## Related constructs diff --git a/docs/reference/macros/cgp_auto_getter.md b/docs/reference/macros/cgp_auto_getter.md index 7ee950cb..e28068d8 100644 --- a/docs/reference/macros/cgp_auto_getter.md +++ b/docs/reference/macros/cgp_auto_getter.md @@ -4,11 +4,13 @@ ## Purpose -`#[cgp_auto_getter]` exists to remove the boilerplate of writing field accessors by hand. Reading a value out of a context is the most basic form of dependency injection in CGP, and the underlying mechanism — `HasField` — is precise but verbose and unfamiliar to most Rust developers. The macro lets you state the intent in ordinary trait-method syntax (`fn name(&self) -> &str;`) and generates the `HasField`-based plumbing for you. +`#[cgp_auto_getter]` exists to publish a context field as a reusable getter *capability* — a named `self.name()` accessor that other providers depend on. Reading a value out of a context is the most basic form of dependency injection in CGP, and the underlying mechanism — `HasField` — is precise but verbose and unfamiliar to most Rust developers. The macro lets you state the intent in ordinary trait-method syntax (`fn name(&self) -> &str;`) and generates the `HasField`-based plumbing for you. -The defining trait-off of `#[cgp_auto_getter]` is that it produces exactly one implementation and no CGP wiring. Unlike a full component, there is no provider trait, no component name, and no delegation table; the macro emits a blanket impl over a generic context, and any context that derives `HasField` with a matching field automatically satisfies the trait. This makes it the lightest-weight getter construct — ideal when the field name in the context always matches the method name and you never need an alternative implementation. +Reach for `#[cgp_auto_getter]` when the field is a shared capability, not when a single provider needs a value for its own use. For the common case of reading a field inside one provider, an [`#[implicit]`](../attributes/implicit.md) argument is the preferred, lighter form: it injects the value as an ordinary-looking parameter with no separate trait to declare. A getter trait earns its keep when the accessor is genuinely shared — depended on by several providers through [`#[uses]`](../attributes/uses.md), or carrying an associated type inferred from the field so the type stays abstract. Both desugar to the same `HasField` bound and share the same access rules, so the choice is about whether the value is a published capability or a private input. -The cost of that simplicity is rigidity. Because the field tag is derived directly from the method name, the context *must* expose a field of exactly that name. When you need the field name to differ from the method name, or you want the getter to be swappable through wiring, reach for [`#[cgp_getter]`](cgp_getter.md) instead, which builds the same convenience on top of a real CGP component. +The defining trade-off of `#[cgp_auto_getter]` is that it produces exactly one implementation and no CGP wiring. Unlike a full component, there is no provider trait, no component name, and no delegation table; the macro emits a blanket impl over a generic context, and any context that derives `HasField` with a matching field automatically satisfies the trait. This makes it the lightest-weight getter construct — ideal when the field name in the context always matches the method name and you never need an alternative implementation. + +The cost of that simplicity is rigidity. Because the field tag is derived directly from the method name, the context *must* expose a field of exactly that name. When you need the field name to differ from the method name, or you want the getter to be swappable through wiring, [`#[cgp_getter]`](cgp_getter.md) builds the same convenience on top of a real CGP component — but that is an advanced tool reserved for when a context needs full control over which field a getter reads from, and most getters do not. Prefer `#[cgp_auto_getter]` for a published accessor and an implicit argument for a private read; reach for `#[cgp_getter]` only when field-name decoupling is specifically wanted. ## Syntax diff --git a/docs/reference/macros/cgp_component.md b/docs/reference/macros/cgp_component.md index a32092dc..eac43b03 100644 --- a/docs/reference/macros/cgp_component.md +++ b/docs/reference/macros/cgp_component.md @@ -36,6 +36,8 @@ The three keys correspond to the three names the macro needs, and each has a def Two companion attributes extend the macro for special cases and are documented separately. Adding [`#[derive_delegate(...)]`](../attributes/derive_delegate.md) generates `UseDelegate` providers that dispatch on a generic parameter, and adding [`#[extend(...)]`](../attributes/extend.md) adds supertrait bounds to the generated consumer trait. The related macros [`#[cgp_type]`](cgp_type.md) and [`#[cgp_getter]`](cgp_getter.md) build on `#[cgp_component]` to derive additional constructs for abstract-type and getter components respectively. +When a component's supertrait exists only to supply an abstract type the trait's own signatures name — most commonly [`HasErrorType`](../components/has_error_type.md), whose `Error` a fallible method returns — prefer importing that type with [`#[use_type]`](../attributes/use_type.md) over writing the supertrait and the qualified `Self::` path by hand. Annotating the trait with `#[use_type(HasErrorType::Error)]` adds `HasErrorType` as a supertrait *and* rewrites a bare `Error` in the signatures to `::Error`, so the definition reads `pub trait CanLoad { fn load(&self, path: &str) -> Result; }` rather than spelling `: HasErrorType` and `Self::Error`. For a supertrait `#[use_type]` cannot express — a capability supertrait with no associated type to import, or a bound the trait does not name in its signatures — declare it with `#[extend(...)]` in preference to native `: Supertrait` syntax, which reads as OOP-style inheritance rather than the capability import a CGP supertrait actually is. A local associated type the trait declares itself, such as a handler's `type Output`, always stays written as `Self::Output`; it is not imported, so `#[use_type]` neither lists nor rewrites it. + ## Syntax Grammar The attribute argument of `#[cgp_component]` is either a bare provider name or a comma-separated set of keyed values: diff --git a/docs/reference/macros/cgp_getter.md b/docs/reference/macros/cgp_getter.md index cddd24cc..d0bfd389 100644 --- a/docs/reference/macros/cgp_getter.md +++ b/docs/reference/macros/cgp_getter.md @@ -4,7 +4,7 @@ ## Purpose -`#[cgp_getter]` exists for getters that need to participate in CGP wiring rather than resolve to a single blanket impl. A getter trait describes a value the context can supply, and most of the time the value comes straight from a same-named field — the case [`#[cgp_auto_getter]`](cgp_auto_getter.md) handles. But CGP code routinely needs more flexibility: the context may store the value under a different field name, or different contexts may supply the value in different ways. `#[cgp_getter]` provides that flexibility by making the getter a genuine component with a provider trait, a component name, and a delegation entry. +`#[cgp_getter]` exists for the advanced case where a getter must participate in CGP wiring rather than resolve to a single blanket impl. It is a specialized tool, not a default: most getters read a same-named field, which an [`#[implicit]`](../attributes/implicit.md) argument (for a private read) or [`#[cgp_auto_getter]`](cgp_auto_getter.md) (for a published accessor) handles with no wiring. Reserve `#[cgp_getter]` for when a context needs full control over the getter's implementation — storing the value under a different field name, or supplying it in different ways per context. It delivers that control by making the getter a genuine component with a provider trait, a component name, and a delegation entry. The trade-off against `#[cgp_auto_getter]` is wireable versus blanket. `#[cgp_auto_getter]` emits one blanket impl that fires automatically for any context whose field name matches the method name; there is nothing to wire and nothing to choose. `#[cgp_getter]` emits a component that a context must wire to a provider, but in exchange the field name is decoupled from the method name and the implementation can be selected per context. You pay one line of wiring to gain that decoupling. diff --git a/docs/reference/macros/cgp_impl.md b/docs/reference/macros/cgp_impl.md index 92e83004..841d6624 100644 --- a/docs/reference/macros/cgp_impl.md +++ b/docs/reference/macros/cgp_impl.md @@ -28,7 +28,7 @@ where The attribute argument has three parts, of which only the provider name is required. The leading **`new`** keyword is optional and, when present, tells the macro to also emit `pub struct RectangleArea;` so you need not declare the provider struct separately. The **provider name** (`RectangleArea` above) is the type that occupies the `Self` position in the generated provider impl. An optional **`: ComponentType`** suffix overrides the component name used in the generated [`IsProviderFor`](../traits/is_provider_for.md) impl; when omitted, the component defaults to the provider trait's name with a `Component` suffix, so implementing `AreaCalculator` targets `AreaCalculatorComponent`. -The `for Context` clause is optional. When you write `impl AreaCalculator` with no `for` and no leading context type parameter, the macro inserts a generic context parameter for you, using the reserved identifier `__Context__`. The block above is therefore equivalent to `impl<__Context__> AreaCalculator for __Context__`, with `self`/`Self` standing in for that inserted parameter. You may instead name the context explicitly — `impl AreaCalculator for Context` — when you need to bound it in the impl generics or refer to it by a readable name; both forms are accepted. +The `for Context` clause is optional, and **omitting it is the preferred form** — the unqualified `impl AreaCalculator` is what makes a provider read like an ordinary trait impl. When you write `impl AreaCalculator` with no `for` and no leading context type parameter, the macro inserts a generic context parameter for you, using the reserved identifier `__Context__`. The block above is therefore equivalent to `impl<__Context__> AreaCalculator for __Context__`, with `self`/`Self` standing in for that inserted parameter. Name the context explicitly — `impl AreaCalculator for Context` — only when you genuinely need it: to bound the context in the impl generics in a way the sugar cannot express, such as a lifetime or higher-ranked bound, or to refer to it by a readable name. Both forms are accepted, but reach for the explicit one only when the implicit one falls short. A provider may be generic. Type parameters on the provider, such as a higher-order provider's inner provider, are written in the `Self` position of the attribute and in the impl generics together: diff --git a/docs/reference/providers/use_default.md b/docs/reference/providers/use_default.md index a1191e0e..558f75e5 100644 --- a/docs/reference/providers/use_default.md +++ b/docs/reference/providers/use_default.md @@ -46,13 +46,14 @@ Provider impls against `UseDefault` are written with empty bodies, so the defaul ```rust #[cgp_impl(UseDefault)] -impl NameGetter for Context {} +impl NameGetter {} #[cgp_impl(UseDefault)] -impl Greeter for Context {} +#[uses(HasName)] +impl Greeter {} ``` -The first impl makes `UseDefault` a `NameGetter` provider whose `name` is the trait default `"John"`; the second makes it a `Greeter` provider whose `greet` is the trait default that formats around `self.name()`. The `Greeter` impl restates the consumer-side dependency `HasName` in its bound, because the default body of `greet` calls `name`. Each `#[cgp_impl]` also generates the matching [`IsProviderFor`](../traits/is_provider_for.md) impl, so the dependency propagates to the [check traits](../../concepts/check-traits.md) exactly as for any other provider. +The first impl makes `UseDefault` a `NameGetter` provider whose `name` is the trait default `"John"`; the second makes it a `Greeter` provider whose `greet` is the trait default that formats around `self.name()`. The `Greeter` impl restates the consumer-side dependency `HasName` with [`#[uses(HasName)]`](../attributes/uses.md), because the default body of `greet` calls `name`. Each `#[cgp_impl]` also generates the matching [`IsProviderFor`](../traits/is_provider_for.md) impl, so the dependency propagates to the [check traits](../../concepts/check-traits.md) exactly as for any other provider. Because `UseDefault` has no generated impls, the author controls precisely which components it serves. A type that has not had a provider impl written for a given component is simply not a provider for it; there is no automatic fallback, and the empty-body `#[cgp_impl]` is the explicit opt-in. @@ -79,10 +80,11 @@ pub trait CanGreet: HasName { } #[cgp_impl(UseDefault)] -impl NameGetter for Context {} +impl NameGetter {} #[cgp_impl(UseDefault)] -impl Greeter for Context {} +#[uses(HasName)] +impl Greeter {} pub struct App; diff --git a/docs/skills/cgp/SKILL.md b/docs/skills/cgp/SKILL.md index 41f3afec..8a184063 100644 --- a/docs/skills/cgp/SKILL.md +++ b/docs/skills/cgp/SKILL.md @@ -205,9 +205,11 @@ where ``` The provider name goes in the attribute argument; a leading `new` keyword also declares the -`struct GreetHello;`. You may omit `for Context` entirely (as above) and the macro inserts the -context parameter; if you write it explicitly it must be declared, as `impl Greeter for -Context`. Remember that `self`/`Self` here mean the context. `#[cgp_impl]` desugars to: +`struct GreetHello;`. **Prefer the unqualified `impl Greeter` form and let the macro insert the +context parameter** — omitting `for Context` is what makes a provider read like an ordinary trait +impl. Write the explicit `impl Greeter for Context` only when you must bound or name the +context readably (for example a lifetime or HRTB the sugar cannot express); it must then be declared +in the impl generics. Remember that `self`/`Self` here mean the context. `#[cgp_impl]` desugars to: ```rust #[cgp_new_provider] @@ -229,6 +231,29 @@ auto-generates the matching `IsProviderFor` impl from the same `where` clause. The attribute argument can override the component name, which otherwise defaults to the provider trait's name plus `Component`. +**Strongly prefer the modern, vanilla-looking idioms when you write CGP, and reach for the explicit +forms only when a construct genuinely cannot express the case.** In order: write providers with +`#[cgp_impl]` (not `#[cgp_provider]`/`#[cgp_new_provider]`) and omit `for Context` so the header +reads `impl Greeter`; declare capability dependencies with [`#[uses(...)]`](functions-and-getters.md) +and inner-provider dependencies with [`#[use_provider(...)]`](higher-order-providers.md) instead of +hand-written `Self:`/`Provider: …` `where` bounds; read context fields with +[`#[implicit]`](functions-and-getters.md) arguments rather than defining a getter trait with +`#[cgp_auto_getter]` (reserve a getter trait for a *published* accessor several providers share, and +`#[cgp_getter]` for the advanced case of choosing the source field per context); add non-type capability supertraits with +[`#[extend(...)]`](functions-and-getters.md) rather than native `: Supertrait` syntax, which reads as +OOP-style inheritance rather than a capability import; and import abstract types (using them as +supertraits) with [`#[use_type]`](abstract-types.md), writing the bare alias (`Scalar`, `Error`) rather than declaring +the trait as a hand-written supertrait or `where Self: HasScalarType` bound and then qualifying every +use as `Self::Scalar`. This applies even to `#[cgp_component]` trait definitions: prefer +`#[use_type(HasErrorType::Error)]` over `: HasErrorType` + `Self::Error`, since the attribute adds the +supertrait for you and lets the signatures read in the bare form. When defining a new dispatchable +component, skip `#[derive_delegate]`/`UseDelegate` and dispatch through the `open` statement or a +namespace instead. The explicit forms remain correct +and are what you *read* in generated code and desugaring; the exceptions that still need them are +narrow — an associated-type-equality bound (`Iterator`) that `#[uses]` cannot spell, a +lifetime or HRTB that forces a named context, or a **local** associated type such as `Self::Output`, +which always stays qualified because it is the trait's own type, not an imported abstract one. + The provider's `where` clause is where **impl-side dependencies** live: `GreetHello` requires `Self: HasName`, but `CanGreet` exposes no such bound, so a caller bounding on `CanGreet` never sees `HasName`. The wiring satisfies each dependency by resolving it through the same context. @@ -417,14 +442,23 @@ fn scaled_rectangle_area(&self, #[implicit] scale_factor: f64) -> f64 { ``` `#[extend(Trait)]` adds *supertrait* bounds to the generated trait — the only way to add supertraits -in `#[cgp_fn]` (whose `where` clauses are impl-side dependencies), and usable on `#[cgp_component]` -too. `#[extend_where(Bound)]` adds `where` clauses to the generated trait definition (`#[cgp_fn]` -only). +in `#[cgp_fn]` (whose `where` clauses are impl-side dependencies), and the **preferred** way to add a +*non-type capability* supertrait on `#[cgp_component]` too: `#[extend(HasName)]` reads as importing a +capability, whereas the native `pub trait CanGreet: HasName` syntax reads as OOP-style inheritance +from a parent class, which a CGP supertrait is not. (When the supertrait is an abstract-type component +whose associated type the signatures name, prefer `#[use_type]` instead — it adds the supertrait *and* +rewrites the type, and is the recommended form for abstract-type components.) `#[extend_where(Bound)]` +adds `where` clauses to the generated trait definition (`#[cgp_fn]` only). ## Getters: `#[cgp_auto_getter]`, `#[cgp_getter]`, `UseField` +Getter traits are for *publishing* a context field as a shared capability, not for a provider +reading a value for its own use — for that, prefer an `#[implicit]` argument (above), which needs no +separate trait. Reach for a getter trait only when the accessor is genuinely shared: depended on by +several providers through `#[uses(HasName)]`, or carrying an associated type inferred from the field. + `#[cgp_auto_getter]` generates a blanket getter impl over `HasField`, with the field name taken from -the method name: +the method name, and is the getter form to prefer: ```rust #[cgp_auto_getter] @@ -437,7 +471,9 @@ pub trait HasName { A single-getter trait may instead declare a local associated type used as the return type, inferred from the field — `trait HasName { type Name; fn name(&self) -> &Self::Name; }`. -`#[cgp_getter]` is like `#[cgp_component]` but also provides a `UseField` blanket impl, so the +`#[cgp_getter]` is an **advanced** tool, reserved for when a context needs full control over which +field a getter reads from — most getters should use `#[cgp_auto_getter]` or an implicit argument +instead. It is like `#[cgp_component]` but also provides a `UseField` blanket impl, so the getter's source field can be chosen by *wiring* rather than fixed to the method name. The `UseField` provider implements a getter by reading the field named `Tag`, which may differ from the method name: @@ -535,23 +571,24 @@ can wire the same shape to different providers. CGP makes the error type abstract so generic code can fail without naming a concrete error. `HasErrorType` (an abstract-type component, `type Error: Debug`) gives a context one shared -`Self::Error`; `CanRaiseError` constructs it from a concrete source error -(`Context::raise_error(source)`); `CanWrapError` attaches detail. Both supertrait +error type; `CanRaiseError` constructs it from a concrete source error +(`Context::raise_error(source)`); `CanWrapError` attaches detail. Both build on `HasErrorType` and are associated-function (no `self`) components that dispatch per source/detail -type: +type. An error-aware trait imports that error type with `#[use_type(HasErrorType::Error)]`, so it +names the error as the bare `Error` instead of writing `: HasErrorType` and `Self::Error` by hand: ```rust #[cgp_component(Loader)] -pub trait CanLoad: HasErrorType { - fn load(&self, path: &str) -> Result; +#[use_type(HasErrorType::Error)] +pub trait CanLoad { + fn load(&self, path: &str) -> Result; } #[cgp_impl(new LoadOrFail)] -impl Loader -where - Self: CanRaiseError, -{ - fn load(&self, path: &str) -> Result { +#[uses(CanRaiseError)] +#[use_type(HasErrorType::Error)] +impl Loader { + fn load(&self, path: &str) -> Result { if path.is_empty() { return Err(Self::raise_error("empty path".to_owned())); } diff --git a/docs/skills/cgp/references/error-handling.md b/docs/skills/cgp/references/error-handling.md index 4badd360..ea6fe7f9 100644 --- a/docs/skills/cgp/references/error-handling.md +++ b/docs/skills/cgp/references/error-handling.md @@ -21,14 +21,15 @@ pub type ErrorOf = ::Error; The `Debug` bound lets `Self::Error` flow into `.unwrap()` and straightforward logging without an extra constraint, and it is enforced on whatever concrete type a context chooses. `ErrorOf` is the convenient spelling of the associated-type path. Generic code that may fail returns `Result` (or `Result>`) and never names a concrete error. -Centralizing the error type on one trait is what lets errors compose. A context trait that may fail supertraits `HasErrorType`, so every such trait refers to the *same* `Self::Error`; if each declared its own associated `Error`, a context bounded by several of them would face several incompatible error types with no way to unify them. `HasErrorType` carries no methods — it only declares the type. The behavior of producing errors lives in the traits that supertrait it. +Centralizing the error type on one trait is what lets errors compose. A context trait that may fail depends on `HasErrorType`, so every such trait refers to the *same* error; if each declared its own associated `Error`, a context bounded by several of them would face several incompatible error types with no way to unify them. `HasErrorType` carries no methods — it only declares the type. The behavior of producing errors lives in the traits that build on it. The preferred way to author such a trait is [`#[use_type(HasErrorType::Error)]`](abstract-types.md), which adds `HasErrorType` as a supertrait *and* rewrites a bare `Error` in the signatures to `::Error` — so you write neither `: HasErrorType` nor `Self::Error` by hand. Because `#[cgp_type]` generates a `UseType` blanket impl, a context fixes its error type by wiring the error-type component to `UseType`, exactly as for any abstract type: ```rust #[cgp_component(Validator)] -pub trait CanValidate: HasErrorType { - fn validate(&self) -> Result<(), Self::Error>; +#[use_type(HasErrorType::Error)] +pub trait CanValidate { + fn validate(&self) -> Result<(), Error>; } pub struct App; @@ -40,23 +41,25 @@ delegate_components! { } ``` -Here `CanValidate` supertraits `HasErrorType`, so its `Self::Error` is the context's shared abstract error, and `App` fixes that error to `String`. The standalone backends (`cgp-error-anyhow`, `cgp-error-eyre`, `cgp-error-std`) supply ready-made providers that set `Error` to their respective library types instead. A context can equally implement the trait directly — `impl HasErrorType for App { type Error = String; }` — which makes plain that it is an ordinary trait with a `Debug`-bounded associated type. +Here `#[use_type(HasErrorType::Error)]` makes `CanValidate` depend on the shared abstract error and lets `validate` name it as the bare `Error`, and `App` fixes that error to `String`. The standalone backends (`cgp-error-anyhow`, `cgp-error-eyre`, `cgp-error-std`) supply ready-made providers that set `Error` to their respective library types instead. A context can equally implement the trait directly — `impl HasErrorType for App { type Error = String; }` — which makes plain that it is an ordinary trait with a `Debug`-bounded associated type. ## `CanRaiseError` and `CanWrapError`: producing and enriching the error -`CanRaiseError` is the consumer trait for turning a concrete source error into the context's abstract `Self::Error`, and `CanWrapError` is the companion that attaches detail to an existing one. Both supertrait `HasErrorType`, and both are `#[cgp_component]`s that delegate per type so a context can handle each source error or detail with a different provider: +`CanRaiseError` is the consumer trait for turning a concrete source error into the context's abstract error, and `CanWrapError` is the companion that attaches detail to an existing one. Both import the error type with `#[use_type(HasErrorType::Error)]` — so they name it as the bare `Error` and gain `HasErrorType` as a supertrait — and both are `#[cgp_component]`s that delegate per type so a context can handle each source error or detail with a different provider: ```rust #[cgp_component(ErrorRaiser)] #[derive_delegate(UseDelegate)] -pub trait CanRaiseError: HasErrorType { - fn raise_error(error: SourceError) -> Self::Error; +#[use_type(HasErrorType::Error)] +pub trait CanRaiseError { + fn raise_error(error: SourceError) -> Error; } #[cgp_component(ErrorWrapper)] #[derive_delegate(UseDelegate)] -pub trait CanWrapError: HasErrorType { - fn wrap_error(error: Self::Error, detail: Detail) -> Self::Error; +#[use_type(HasErrorType::Error)] +pub trait CanWrapError { + fn wrap_error(error: Error, detail: Detail) -> Error; } ``` @@ -66,19 +69,19 @@ A provider written against these bounds names neither the context nor its concre ```rust #[cgp_component(Loader)] -pub trait CanLoad: HasErrorType { - fn load(&self, path: &str) -> Result; +#[use_type(HasErrorType::Error)] +pub trait CanLoad { + fn load(&self, path: &str) -> Result; } #[cgp_impl(new LoadOrFail)] -impl Loader for Context -where - Context: CanRaiseError + CanWrapError, -{ - fn load(&self, path: &str) -> Result { +#[uses(CanRaiseError, CanWrapError)] +#[use_type(HasErrorType::Error)] +impl Loader { + fn load(&self, path: &str) -> Result { if path.is_empty() { - let err = Context::raise_error("empty path".to_owned()); - return Err(Context::wrap_error(err, format!("while loading {path}"))); + let err = Self::raise_error("empty path".to_owned()); + return Err(Self::wrap_error(err, format!("while loading {path}"))); } Ok(format!("contents of {path}")) } diff --git a/docs/skills/cgp/references/functions-and-getters.md b/docs/skills/cgp/references/functions-and-getters.md index e24a7b36..e23a9057 100644 --- a/docs/skills/cgp/references/functions-and-getters.md +++ b/docs/skills/cgp/references/functions-and-getters.md @@ -142,7 +142,7 @@ This adds `Self: RectangleArea` to the generated impl's `where` clause, alongsid ## Adding supertraits and trait bounds: `#[extend]` and `#[extend_where]` -In `#[cgp_fn]`, the function's own `where` clauses are impl-side dependencies kept off the trait, so there is no place to write a supertrait by hand — `#[extend(...)]` is the only way to add one. Where `#[uses]` adds a hidden impl-side bound (the `use` equivalent), `#[extend]` promotes its bound to a *supertrait* of the generated trait — a public requirement every implementor satisfies and every caller may rely on (the `pub use` equivalent). The bound appears in two places: as a supertrait on the trait, so an associated type like `Self::Scalar` resolves and callers know the bound holds, and in the impl's `where` clause so the body can use it. A `#[cgp_fn]` over an abstract scalar type: +In `#[cgp_fn]`, the function's own `where` clauses are impl-side dependencies kept off the trait, so there is no place to write a supertrait by hand — `#[extend(...)]` is the only way to add one. Where `#[uses]` adds a hidden impl-side bound (the `use` equivalent), `#[extend]` promotes its bound to a *supertrait* of the generated trait — a public requirement every implementor satisfies and every caller may rely on (the `pub use` equivalent). The bound appears in two places: as a supertrait on the trait, so an associated type like `Self::Scalar` resolves and callers know the bound holds, and in the impl's `where` clause so the body can use it. The example below uses the abstract-type trait `HasScalarType` to make that two-placement behavior visible in one signature, but for an abstract-type supertrait like this, `#[use_type]` is the production form (see the note after it) and `#[extend]` is reserved for a non-type capability supertrait. A `#[cgp_fn]` over an abstract scalar type: ```rust pub trait HasScalarType { @@ -161,7 +161,7 @@ pub fn rectangle_area( // → pub trait RectangleArea: HasScalarType { fn rectangle_area(&self) -> Self::Scalar; } ``` -`#[extend]` accepts the same simplified `TraitIdent` syntax as `#[uses]`, and is also usable on `#[cgp_component]` (where it simply duplicates the native `pub trait Foo: Bar` supertrait syntax for stylistic consistency). Its sibling `#[extend_where(...)]` adds *`where`-clause* predicates to the generated trait definition rather than supertraits, and is `#[cgp_fn]`-only. Unlike `#[uses]` and `#[extend]`, it accepts arbitrary predicates — including associated-type equality — so a generic parameter can carry a publicly visible bound: +`#[extend]` accepts the same simplified `TraitIdent` syntax as `#[uses]`, and is also usable on `#[cgp_component]`, where it is the preferred way to add a *non-type capability* supertrait: writing `#[extend(HasName)]` reads as importing a capability, whereas the native `pub trait CanGreet: HasName` syntax reads as OOP-style inheritance from a parent class, which a CGP supertrait is not. (For an abstract-type component whose associated type the signatures name, prefer `#[use_type]` instead, which adds the supertrait *and* rewrites the type — the recommended form for abstract-type components.) Its sibling `#[extend_where(...)]` adds *`where`-clause* predicates to the generated trait definition rather than supertraits, and is `#[cgp_fn]`-only. Unlike `#[uses]` and `#[extend]`, it accepts arbitrary predicates — including associated-type equality — so a generic parameter can carry a publicly visible bound: ```rust #[cgp_fn] @@ -177,7 +177,7 @@ The `Scalar: Mul` bound from the body stays an impl-side dependency, while `Scal ## Getter traits: `#[cgp_auto_getter]` -A getter trait exposes a context field as a reusable `self.name()` accessor, and `#[cgp_auto_getter]` generates its single blanket impl by reading the field whose name matches the method name. Where an [implicit argument](#field-access-dressed-as-a-function-argument-implicit) injects a value once at the start of one method, a getter is the better tool when the same field is read across many methods or part-way through a body. The macro takes no arguments and re-emits the trait verbatim, adding a blanket impl over `__Context__` keyed by the method name as a `Symbol!`: +A getter trait exposes a context field as a reusable `self.name()` accessor, and `#[cgp_auto_getter]` generates its single blanket impl by reading the field whose name matches the method name. Reserve it for the case where the field is a *published capability* — an accessor other providers depend on through `#[uses(HasName)]`, or one carrying an associated type inferred from the field — not for a provider reading a value for its own use. For that common case, prefer an [implicit argument](#field-access-dressed-as-a-function-argument-implicit): it injects the value as an ordinary-looking parameter with no separate trait to declare, and a value used throughout a body is simply bound once at the top. The macro takes no arguments and re-emits the trait verbatim, adding a blanket impl over `__Context__` keyed by the method name as a `Symbol!`: ```rust #[cgp_auto_getter] @@ -211,11 +211,11 @@ pub trait HasName { // { type Name = Name; … } ``` -A context gains the getter just by deriving `HasField` with a matching field — `person.name()` resolves through the blanket impl with no wiring. The cost of that simplicity is rigidity: the field name *must* equal the method name, and there is no way to swap the implementation. When you need either, reach for `#[cgp_getter]`. +A context gains the getter just by deriving `HasField` with a matching field — `person.name()` resolves through the blanket impl with no wiring. The cost of that simplicity is rigidity: the field name *must* equal the method name, and there is no way to swap the implementation. When you need either, `#[cgp_getter]` provides it — but that is an advanced tool, not a routine next step. ## Wireable getters: `#[cgp_getter]` and `UseField` -`#[cgp_getter]` defines a getter as a full CGP [component](components.md) instead of a blanket impl, so the field name can differ from the method name and the getter can be swapped per context through [wiring](wiring.md). It accepts the same getter-method forms as `#[cgp_auto_getter]`, but because it is an extension of `#[cgp_component]` it needs a provider trait name. The default derives one from the trait name by stripping a leading `Has` and appending `Getter`, so `HasName` yields the provider `NameGetter` and the component marker `NameGetterComponent`; pass an argument like `#[cgp_getter(GetName)]` to override it. +`#[cgp_getter]` defines a getter as a full CGP [component](components.md) instead of a blanket impl, so the field name can differ from the method name and the getter can be swapped per context through [wiring](wiring.md). It is a specialized, advanced tool: reserve it for when a context genuinely needs full control over which field a getter reads from, and prefer an implicit argument or `#[cgp_auto_getter]` for the ordinary case of a same-named field. It accepts the same getter-method forms as `#[cgp_auto_getter]`, but because it is an extension of `#[cgp_component]` it needs a provider trait name. The default derives one from the trait name by stripping a leading `Has` and appending `Getter`, so `HasName` yields the provider `NameGetter` and the component marker `NameGetterComponent`; pass an argument like `#[cgp_getter(GetName)]` to override it. The decoupling is delivered by an automatically generated `UseField` provider impl. `UseField` is a zero-sized provider (a `PhantomData`-only marker named in wiring, carrying no runtime value) that implements the getter by reading the field named `Tag` from the context — and crucially, `Tag` need not be the method name: @@ -262,7 +262,7 @@ The explicit form is more verbose but requires no understanding of `HasField` or ## Choosing between the constructs -The constructs here divide along two axes: how the value is read, and how flexible the implementation is. For a value consumed once at the start of a method, an `#[implicit]` argument keeps the access local and the code reading like a plain function. For a field read across several methods or mid-body, a getter trait exposes it as a reusable accessor; pick `#[cgp_auto_getter]` when the field name always matches the method name and no alternative is needed, and `#[cgp_getter]` (with `UseField`) when the field name must differ or the getter must be swappable per context. For a whole capability rather than a single field, `#[cgp_fn]` defines one with no wiring when a single implementation suffices, and a full [component](components.md) when many providers must coexist. All of them rest on the same `HasField` machinery and the same access rules, so mixing them carries no conceptual overhead. +The constructs here divide along two axes: whether the value is a private input or a published capability, and how much control the implementation needs. For reading a field into a provider — the common case — an `#[implicit]` argument is the default: it keeps the access local and the code reading like a plain function, whether the value is used once or throughout the body. Promote a field to a getter trait only when it is a shared capability: `#[cgp_auto_getter]` when the field name matches the method name (the usual getter), and `#[cgp_getter]` (with `UseField`) as the advanced tool reserved for when the source field name must be chosen per context at wiring time. For a whole capability rather than a single field, `#[cgp_fn]` defines one with no wiring when a single implementation suffices, and a full [component](components.md) when many providers must coexist. All of them rest on the same `HasField` machinery and the same access rules, so mixing them carries no conceptual overhead. ## Further reference diff --git a/docs/skills/cgp/references/handlers.md b/docs/skills/cgp/references/handlers.md index 3350f45f..5c574baf 100644 --- a/docs/skills/cgp/references/handlers.md +++ b/docs/skills/cgp/references/handlers.md @@ -34,10 +34,11 @@ The provider trait `Computer` moves the context into an ex #[cgp_component(TryComputer)] #[derive_delegate(UseDelegate)] #[derive_delegate(UseInputDelegate)] -pub trait CanTryCompute: HasErrorType { +#[use_type(HasErrorType::Error)] +pub trait CanTryCompute { type Output; fn try_compute(&self, _code: PhantomData, input: Input) - -> Result; + -> Result; } ``` @@ -50,10 +51,11 @@ A `TryComputer` provider carries a `Context: HasErrorType` bound and typically c #[cgp_component(Handler)] #[derive_delegate(UseDelegate)] #[derive_delegate(UseInputDelegate)] -pub trait CanHandle: HasErrorType { +#[use_type(HasErrorType::Error)] +pub trait CanHandle { type Output; async fn handle(&self, _tag: PhantomData, input: Input) - -> Result; + -> Result; } ``` @@ -82,16 +84,18 @@ The runner pair executes a unit of work selected by a `Code` tag rather than tra #[cgp_component(Runner)] #[async_trait] #[derive_delegate(UseDelegate)] -pub trait CanRun: HasErrorType { - async fn run(&self, _code: PhantomData) -> Result<(), Self::Error>; +#[use_type(HasErrorType::Error)] +pub trait CanRun { + async fn run(&self, _code: PhantomData) -> Result<(), Error>; } #[cgp_component(SendRunner)] #[async_trait] #[derive_delegate(UseDelegate)] -pub trait CanSendRun: HasErrorType { +#[use_type(HasErrorType::Error)] +pub trait CanSendRun { fn send_run(&self, _code: PhantomData) - -> impl Future> + Send; + -> impl Future> + Send; } ``` diff --git a/docs/skills/cgp/references/higher-order-providers.md b/docs/skills/cgp/references/higher-order-providers.md index e440ef2e..ab11d50e 100644 --- a/docs/skills/cgp/references/higher-order-providers.md +++ b/docs/skills/cgp/references/higher-order-providers.md @@ -137,8 +137,9 @@ The real leverage of generic-parameter dispatch appears when the main target of ```rust #[cgp_component(AreaCalculator)] -pub trait CanCalculateAreaOfShape: HasScalarType { - fn area_of_shape(&self, shape: &Shape) -> Self::Scalar; +#[use_type(HasScalarType::Scalar)] +pub trait CanCalculateAreaOfShape { + fn area_of_shape(&self, shape: &Shape) -> Scalar; } ``` diff --git a/docs/skills/cgp/references/modularity-hierarchy.md b/docs/skills/cgp/references/modularity-hierarchy.md index 57a55437..855a9bbc 100644 --- a/docs/skills/cgp/references/modularity-hierarchy.md +++ b/docs/skills/cgp/references/modularity-hierarchy.md @@ -130,10 +130,10 @@ The finest grain overrides wiring *inside* a provider rather than at the context pub struct SerializeIteratorWith(pub PhantomData); #[cgp_impl(SerializeIteratorWith)] -impl ValueSerializer for Context +impl ValueSerializer where for<'a> &'a Value: IntoIterator, - Provider: for<'a> ValueSerializer::Item>, + Provider: for<'a> ValueSerializer::Item>, { fn serialize(&self, value: &Value, serializer: S) -> Result where