From 585ebc43b812188462335855122d3b971b3d70ca Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Wed, 8 Jul 2026 23:37:35 +0200 Subject: [PATCH 1/5] Add more error tests --- .../higher_order_inner_dependency.rs | 79 +++++++++++++++++++ .../higher_order_inner_dependency.stderr | 75 ++++++++++++++++++ .../higher_order_outer_dependency.rs | 77 ++++++++++++++++++ .../higher_order_outer_dependency.stderr | 38 +++++++++ .../missing_has_field_derive.rs | 62 +++++++++++++++ .../missing_has_field_derive.stderr | 37 +++++++++ docs/errors/README.md | 1 + docs/errors/checks/check-trait-failure.md | 7 ++ .../checks/higher-order-provider-layer.md | 71 +++++++++++++++++ docs/errors/checks/verbose-cascade.md | 1 + docs/errors/error_codes/e0277.md | 1 + docs/guides/debugging.md | 2 +- 12 files changed, 450 insertions(+), 1 deletion(-) create mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_inner_dependency.rs create mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_inner_dependency.stderr create mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_outer_dependency.rs create mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_outer_dependency.stderr create mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_has_field_derive.rs create mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_has_field_derive.stderr create mode 100644 docs/errors/checks/higher-order-provider-layer.md diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_inner_dependency.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_inner_dependency.rs new file mode 100644 index 00000000..c73c2a30 --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_inner_dependency.rs @@ -0,0 +1,79 @@ +//! Acceptable failure: a higher-order provider whose *inner* layer carries the +//! unmet dependency. `ScaledArea` delegates to an inner `AreaCalculator` +//! and adds its own `Self: HasScaleFactor` dependency; `BaseArea` is that inner +//! provider and needs `Self: HasBaseArea`. `Rectangle` supplies `scale_factor` +//! but not `base_area`, so the *outer* layer's dependency holds and the *inner* +//! layer's fails. +//! +//! This fixture pins where the diagnostic locates the failing layer: the +//! `unsatisfied trait bound introduced here` caret lands on `BaseArea`'s +//! `Self: HasBaseArea` clause (the inner provider), and the `required for …` chain +//! runs *through* `ScaledArea`'s `IsProviderFor` before reaching +//! `CanUseComponent`, so the outer wrapper appears in the chain even though its own +//! bound is satisfied. Contrast higher_order_outer_dependency.rs, whose caret lands +//! on `ScaledArea`'s own clause and whose chain never reaches the inner provider — +//! the two failures are structurally similar but point at different layers, which +//! is why `#[check_providers(...)]` exists to assert `IsProviderFor` per layer. +//! This is the check doing its job, not a macro defect. +//! +//! See docs/errors/checks/higher-order-provider-layer.md. + +use cgp::prelude::*; + +#[cgp_component(AreaCalculator)] +pub trait CanCalculateArea { + fn area(&self) -> f64; +} + +#[cgp_auto_getter] +pub trait HasBaseArea { + fn base_area(&self) -> f64; +} + +#[cgp_auto_getter] +pub trait HasScaleFactor { + fn scale_factor(&self) -> f64; +} + +#[cgp_impl(new BaseArea)] +impl AreaCalculator +where + Self: HasBaseArea, +{ + fn area(&self) -> f64 { + self.base_area() + } +} + +#[cgp_impl(new ScaledArea)] +#[use_provider(Inner: AreaCalculator)] +impl AreaCalculator +where + Self: HasScaleFactor, +{ + fn area(&self) -> f64 { + self.scale_factor() * Inner::area(self) + } +} + +#[derive(HasField)] +pub struct Rectangle { + pub scale_factor: f64, + // missing `base_area`, so the inner `BaseArea` layer fails +} + +delegate_components! { + Rectangle { + AreaCalculatorComponent: ScaledArea, + } +} + +// Fails in the inner `BaseArea` layer: `Rectangle` has `scale_factor` but not +// `base_area`, so `ScaledArea`'s own dependency holds and `BaseArea`'s does not. +check_components! { + Rectangle { + AreaCalculatorComponent, + } +} + +fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_inner_dependency.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_inner_dependency.stderr new file mode 100644 index 00000000..63111d57 --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_inner_dependency.stderr @@ -0,0 +1,75 @@ +error[E0277]: the trait bound `BaseArea: AreaCalculator` is not satisfied + --> tests/acceptable/check_components/higher_order_inner_dependency.rs:75:9 + | +75 | AreaCalculatorComponent, + | ^^^^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound + | +help: the trait `AreaCalculator` is not implemented for `BaseArea` + --> tests/acceptable/check_components/higher_order_inner_dependency.rs:38:16 + | +38 | #[cgp_impl(new BaseArea)] + | ^^^^^^^^ +help: the trait `AreaCalculator<__Context__>` is implemented for `BaseArea` + --> tests/acceptable/check_components/higher_order_inner_dependency.rs:39:1 + | +39 | / impl AreaCalculator +40 | | where +41 | | Self: HasBaseArea, + | |______________________^ +note: required for `ScaledArea` to implement `IsProviderFor` + --> tests/acceptable/check_components/higher_order_inner_dependency.rs:48:1 + | +48 | #[cgp_impl(new ScaledArea)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: required for `Rectangle` to implement `CanUseComponent` +note: required by a bound in `__CheckRectangle` + --> tests/acceptable/check_components/higher_order_inner_dependency.rs:73:1 + | +73 | / check_components! { +74 | | Rectangle { +75 | | AreaCalculatorComponent, +76 | | } +77 | | } + | |_^ required by this bound in `__CheckRectangle` + = note: this error originates in the attribute macro `cgp_impl` which comes from the expansion of the macro `check_components` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Rectangle: CanUseComponent` is not satisfied + --> tests/acceptable/check_components/higher_order_inner_dependency.rs:75:9 + | +75 | AreaCalculatorComponent, + | ^^^^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound + | +help: the trait `HasField>>>>>>>>>>` is not implemented for `Rectangle` + but trait `HasField>>>>>>>>>>>>>` is implemented for it + --> tests/acceptable/check_components/higher_order_inner_dependency.rs:59:10 + | +59 | #[derive(HasField)] + | ^^^^^^^^ +note: required for `Rectangle` to implement `HasBaseArea` + --> tests/acceptable/check_components/higher_order_inner_dependency.rs:28:1 + | +28 | #[cgp_auto_getter] + | ^^^^^^^^^^^^^^^^^^ +29 | pub trait HasBaseArea { + | ^^^^^^^^^^^ +note: required for `BaseArea` to implement `IsProviderFor` + --> tests/acceptable/check_components/higher_order_inner_dependency.rs:38:1 + | +38 | #[cgp_impl(new BaseArea)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ +... +41 | Self: HasBaseArea, + | ----------- unsatisfied trait bound introduced here + = note: 1 redundant requirement hidden + = note: required for `ScaledArea` to implement `IsProviderFor` + = note: required for `Rectangle` to implement `CanUseComponent` +note: required by a bound in `__CheckRectangle` + --> tests/acceptable/check_components/higher_order_inner_dependency.rs:73:1 + | +73 | / check_components! { +74 | | Rectangle { +75 | | AreaCalculatorComponent, +76 | | } +77 | | } + | |_^ required by this bound in `__CheckRectangle` + = note: this error originates in the derive macro `HasField` which comes from the expansion of the macro `check_components` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_outer_dependency.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_outer_dependency.rs new file mode 100644 index 00000000..c8180b6f --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_outer_dependency.rs @@ -0,0 +1,77 @@ +//! Acceptable failure: the mirror of higher_order_inner_dependency.rs, where the +//! *outer* layer of the same higher-order provider carries the unmet dependency. +//! `ScaledArea` is wired again, but now `Rectangle` supplies `base_area` +//! and not `scale_factor`, so the *inner* `BaseArea` layer would succeed and the +//! *outer* `ScaledArea` layer fails on its own `Self: HasScaleFactor`. +//! +//! This fixture pins the contrast with the inner-failure case: the `unsatisfied +//! trait bound introduced here` caret lands on `ScaledArea`'s own +//! `Self: HasScaleFactor` clause, and the `required for …` chain is *shorter* — it +//! reaches `ScaledArea`'s `IsProviderFor` and stops, never descending +//! into `BaseArea`, because the outer layer fails before it ever delegates inward. +//! Read alongside higher_order_inner_dependency.rs, the pair shows that the layer +//! at fault is identified by which provider's `where` clause the caret sits on and +//! how deep the chain runs. This is the check doing its job, not a macro defect. +//! +//! See docs/errors/checks/higher-order-provider-layer.md. + +use cgp::prelude::*; + +#[cgp_component(AreaCalculator)] +pub trait CanCalculateArea { + fn area(&self) -> f64; +} + +#[cgp_auto_getter] +pub trait HasBaseArea { + fn base_area(&self) -> f64; +} + +#[cgp_auto_getter] +pub trait HasScaleFactor { + fn scale_factor(&self) -> f64; +} + +#[cgp_impl(new BaseArea)] +impl AreaCalculator +where + Self: HasBaseArea, +{ + fn area(&self) -> f64 { + self.base_area() + } +} + +#[cgp_impl(new ScaledArea)] +#[use_provider(Inner: AreaCalculator)] +impl AreaCalculator +where + Self: HasScaleFactor, +{ + fn area(&self) -> f64 { + self.scale_factor() * Inner::area(self) + } +} + +#[derive(HasField)] +pub struct Rectangle { + pub base_area: f64, + // missing `scale_factor`, so the outer `ScaledArea` layer fails +} + +delegate_components! { + Rectangle { + AreaCalculatorComponent: ScaledArea, + } +} + +// Fails in the outer `ScaledArea` layer: `Rectangle` has `base_area` but not +// `scale_factor`, so the inner `BaseArea` dependency holds and `ScaledArea`'s does +// not. +check_components! { + Rectangle { + AreaCalculatorComponent, + } +} + +fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_outer_dependency.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_outer_dependency.stderr new file mode 100644 index 00000000..370ea6ed --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_outer_dependency.stderr @@ -0,0 +1,38 @@ +error[E0277]: the trait bound `Rectangle: CanUseComponent` is not satisfied + --> tests/acceptable/check_components/higher_order_outer_dependency.rs:73:9 + | +73 | AreaCalculatorComponent, + | ^^^^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound + | +help: the trait `HasField>>>>>>>>>>>>>` is not implemented for `Rectangle` + but trait `HasField>>>>>>>>>>` is implemented for it + --> tests/acceptable/check_components/higher_order_outer_dependency.rs:56:10 + | +56 | #[derive(HasField)] + | ^^^^^^^^ +note: required for `Rectangle` to implement `HasScaleFactor` + --> tests/acceptable/check_components/higher_order_outer_dependency.rs:30:1 + | +30 | #[cgp_auto_getter] + | ^^^^^^^^^^^^^^^^^^ +31 | pub trait HasScaleFactor { + | ^^^^^^^^^^^^^^ +note: required for `ScaledArea` to implement `IsProviderFor` + --> tests/acceptable/check_components/higher_order_outer_dependency.rs:45:1 + | +45 | #[cgp_impl(new ScaledArea)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +49 | Self: HasScaleFactor, + | -------------- unsatisfied trait bound introduced here + = note: required for `Rectangle` to implement `CanUseComponent` +note: required by a bound in `__CheckRectangle` + --> tests/acceptable/check_components/higher_order_outer_dependency.rs:71:1 + | +71 | / check_components! { +72 | | Rectangle { +73 | | AreaCalculatorComponent, +74 | | } +75 | | } + | |_^ required by this bound in `__CheckRectangle` + = note: this error originates in the derive macro `HasField` which comes from the expansion of the macro `check_components` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_has_field_derive.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_has_field_derive.rs new file mode 100644 index 00000000..c06d3830 --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_has_field_derive.rs @@ -0,0 +1,62 @@ +//! Acceptable failure: a variant of check_components/missing_dependency.rs where +//! the mistake is not a missing *field* but a missing `#[derive(HasField)]` +//! altogether. `GreetHello` needs `Self: HasName`, and `Person` even has a `name` +//! field — but without the derive, `Person` has *no* `HasField` impls at all, so +//! `HasName` is unsatisfiable and the check fails. +//! +//! This fixture pins the diagnostic that tells this case apart from a single +//! missing field: the `help:` note names `HasField` as +//! unimplemented for `Person` and points at the `Person` struct, but — unlike +//! missing_dependency.rs, where a derived `age` field supplies a "but trait +//! `HasField` is implemented for it" landmark — there is no such +//! landmark here, because `Person` implements the trait for no field. The absence +//! of the landmark is the signal that the whole derive is missing and the fix is +//! to add `#[derive(HasField)]`, not to add a field. This is the check doing its +//! job, not a macro defect. +//! +//! See docs/errors/checks/check-trait-failure.md (the "when the derive is missing +//! entirely" variant). + +use cgp::prelude::*; + +#[cgp_component(Greeter)] +pub trait CanGreet { + fn greet(&self); +} + +#[cgp_auto_getter] +pub trait HasName { + fn name(&self) -> &str; +} + +#[cgp_impl(new GreetHello)] +impl Greeter +where + Self: HasName, +{ + fn greet(&self) { + let _ = self.name(); + } +} + +// The `name` field exists, but without `#[derive(HasField)]` there is no +// `HasField` impl, so `Person` cannot implement `HasName`. +pub struct Person { + pub name: String, +} + +delegate_components! { + Person { + GreeterComponent: GreetHello, + } +} + +// Fails because `Person` has no `HasField` impls at all, not because it lacks the +// `name` field — the fix is the missing derive. +check_components! { + Person { + GreeterComponent, + } +} + +fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_has_field_derive.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_has_field_derive.stderr new file mode 100644 index 00000000..ff3852d6 --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_has_field_derive.stderr @@ -0,0 +1,37 @@ +error[E0277]: the trait bound `Person: CanUseComponent` is not satisfied + --> tests/acceptable/check_components/missing_has_field_derive.rs:58:9 + | +58 | GreeterComponent, + | ^^^^^^^^^^^^^^^^ unsatisfied trait bound + | +help: the trait `cgp::prelude::HasField>>>>>` is not implemented for `Person` + --> tests/acceptable/check_components/missing_has_field_derive.rs:44:1 + | +44 | pub struct Person { + | ^^^^^^^^^^^^^^^^^ +note: required for `Person` to implement `HasName` + --> tests/acceptable/check_components/missing_has_field_derive.rs:27:1 + | +27 | #[cgp_auto_getter] + | ^^^^^^^^^^^^^^^^^^ +28 | pub trait HasName { + | ^^^^^^^ +note: required for `GreetHello` to implement `IsProviderFor` + --> tests/acceptable/check_components/missing_has_field_derive.rs:32:1 + | +32 | #[cgp_impl(new GreetHello)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +35 | Self: HasName, + | ------- unsatisfied trait bound introduced here + = note: required for `Person` to implement `CanUseComponent` +note: required by a bound in `__CheckPerson` + --> tests/acceptable/check_components/missing_has_field_derive.rs:56:1 + | +56 | / check_components! { +57 | | Person { +58 | | GreeterComponent, +59 | | } +60 | | } + | |_^ required by this bound in `__CheckPerson` + = note: this error originates in the attribute macro `cgp_auto_getter` which comes from the expansion of the macro `check_components` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/docs/errors/README.md b/docs/errors/README.md index 4d6e5a6f..3c0c6e2e 100644 --- a/docs/errors/README.md +++ b/docs/errors/README.md @@ -61,6 +61,7 @@ Surfaced and cascading errors — [checks/](checks/): - [Check-trait failure (surfaced)](checks/check-trait-failure.md) — the same unmet dependency forced through `check_components!`, where `IsProviderFor` surfaces the concrete missing bound (a `HasField` or CGP capability) at the wiring site. - [Unsatisfied ordinary trait bound (surfaced)](checks/ordinary-trait-bound.md) — an impl-side dependency that is an *ordinary* Rust trait (`Eq`, `Clone`, …) on an abstract type or impl generic, unmet by the concrete type the context supplies; a check surfaces the ordinary bound (`f64: Eq`) as the primary `E0277`. - [Verbose dependency cascade](checks/verbose-cascade.md) — one deep mistake reported at every transitively dependent provider, and how to locate the single root cause among the repeats. +- [Higher-order provider layer failure (surfaced)](checks/higher-order-provider-layer.md) — a checked higher-order provider with an unmet dependency, where the diagnostic's shape (chain depth, which `where` clause the caret sits on) identifies whether the inner or the outer layer is at fault. - [Unregistered namespace path](checks/unregistered-namespace-path.md) — a component routed through a joined namespace to a path that no entry ever binds, so the *lookup* finds no delegate; a check surfaces it as an `E0277` on the path-keyed `DefaultNamespace`/`DelegateComponent` bound. Structural wiring errors — [wiring/](wiring/): diff --git a/docs/errors/checks/check-trait-failure.md b/docs/errors/checks/check-trait-failure.md index 2f5fed59..3a527bca 100644 --- a/docs/errors/checks/check-trait-failure.md +++ b/docs/errors/checks/check-trait-failure.md @@ -60,6 +60,12 @@ What makes the cause visible is that the check produces a *direct* trait obligat The root cause is **present**, and it is near the top — in the compiler's `help:` note, not at the end of the output. This is the opposite of what a reader might expect from a long note chain: the concrete unmet bound (`HasField`) is stated early, and the `required for …` notes that follow build *outward* from it toward the check trait, rather than drilling down to it. The caret's position is the other half of the value — it sits on the wiring entry the user controls, so the error points at the fix site rather than at a distant call. When many providers depend transitively on one leaf, the output multiplies into a [verbose cascade](verbose-cascade.md), and the position guidance shifts to *which block* carries the actionable cause; for a single checked component it is simply the `help:` note. +## When the derive is missing entirely + +A distinct sub-case of this class is worth recognizing because its fix differs and its diagnostic drops the usual landmark: the mistake is not a missing *field* but a missing `#[derive(HasField)]` on the context altogether. When the struct has the field the getter names but no derive, it has *no* `HasField` impls at all, so the getter trait is still unsatisfiable and the check still fails with the same `CanUseComponent` / `IsProviderFor` / `HasField` shape. The tell is what is *absent*: the `help:` note names the missing `HasField` and points at the `struct` definition, but there is **no** "but trait `HasField<…>` is implemented for it" line, because the context implements the trait for no field at all. The near-impl hint that a single missing field always produces cannot appear when every field is missing. + +Read the absence as its own signal: a checked context that implements `HasField` for nothing behind a `#[derive(HasField)]`-shaped requirement has most likely forgotten the derive, and the fix is to add `#[derive(HasField)]` to the struct, not to add fields one at a time. A tool handling this class should special-case it — zero `HasField` impls plus an unmet `HasField` leaf means the derive, and the headline should say so rather than sending the user after a single field. + ## Resolving it The fix is what the diagnostic already points to: satisfy the named leaf bound. Add the `name` field to `Person`, or wire the getter component to the existing field, so `Person` implements `HasName` and `GreetHello` becomes a valid provider for the component. Because the check surfaced both the concrete bound *and* the wiring entry, no further tracing is usually needed — which is exactly why the standard remedy for a [hidden](../hidden/unsatisfied-dependency.md) failure is to add a check and read this class instead. @@ -73,6 +79,7 @@ For a `cargo-cgp`-style post-processor, the fact to extract as the headline is t - [acceptable/check_components/missing_dependency.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_dependency.rs) — the surfaced `E0277` for `GreetHello`'s unmet `Self: HasName`, whose `.stderr` pins the `help:` note naming `HasField` and the caret landing on `GreeterComponent` inside the block. Its unchecked counterpart, [acceptable/delegate_components/missing_dependency.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/missing_dependency.rs), pins the [hidden](../hidden/unsatisfied-dependency.md) `E0599` for the same mistake. The fused `delegate_and_check_components!` form produces the same surfaced shape. - [acceptable/cgp_component/use_type_foreign_unsatisfied.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/use_type_foreign_unsatisfied.rs) — the capability bound reached through a `#[use_type]` foreign import instead of a check: naming a component for a `Types` that does not implement the imported `HasScalarType` surfaces `E0277` on `NoScalar: HasScalarType`, pinning that the foreign bound is *enforced on the generated trait* rather than silently dropped. - [acceptable/cgp_fn/use_type_nested_unsatisfied.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs) — the same, but through a *nested* two-hop import, so the grounded bound `::Types: HasScalarType` is the one enforced; its `.stderr` pins the `required by this bound in GetScalar` note at the `HasScalarType.Scalar in Types` attribute, confirming the transitively-grounded foreign bound is checked at depth. +- [acceptable/check_components/missing_has_field_derive.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_has_field_derive.rs) — the [derive-missing variant](#when-the-derive-is-missing-entirely): a `Person` that has the `name` field but no `#[derive(HasField)]`, so the check fails with the same shape but *without* the "but trait `HasField<…>` is implemented" landmark, since `Person` implements the trait for no field. Its `.stderr` pins that absent landmark as the signal the whole derive is missing. ## Related diff --git a/docs/errors/checks/higher-order-provider-layer.md b/docs/errors/checks/higher-order-provider-layer.md new file mode 100644 index 00000000..9938879f --- /dev/null +++ b/docs/errors/checks/higher-order-provider-layer.md @@ -0,0 +1,71 @@ +# Higher-order provider layer failure (surfaced) + +A checked higher-order provider whose dependency is unmet surfaces the real leaf bound (`E0277`) like any [check-trait failure](check-trait-failure.md), but the diagnostic's shape also tells you *which layer* of the provider stack failed — and reading that shape is the difference between fixing the wrapper and fixing what it wraps. + +## What triggers it + +This class arises when a [higher-order provider](../../concepts/higher-order-providers.md) — a provider parameterized by another provider — is wired onto a context that cannot meet a dependency in one of its layers, and the wiring is checked. The outer provider carries its own impl-side dependency, and the inner provider it wraps carries a different one; the context may satisfy either, both, or neither. When exactly one layer's dependency is unmet, the check still fails, and the question a reader must answer is which layer to fix. + +```rust +#[cgp_impl(new BaseArea)] +impl AreaCalculator +where + Self: HasBaseArea, // the INNER layer's dependency +{ /* … */ } + +#[cgp_impl(new ScaledArea)] +#[use_provider(Inner: AreaCalculator)] +impl AreaCalculator +where + Self: HasScaleFactor, // the OUTER layer's dependency +{ /* … */ } + +#[derive(HasField)] +pub struct Rectangle { + pub scale_factor: f64, // present: the outer layer is satisfied + // no `base_area`: the inner layer is not +} + +delegate_components! { + Rectangle { AreaCalculatorComponent: ScaledArea } +} + +check_components! { + Rectangle { AreaCalculatorComponent } +} +``` + +## The diagnostic + +This is a **surfaced** class: the `help:` note names the concrete unmet leaf exactly as in a single-layer [check-trait failure](check-trait-failure.md), and the leaf tells you the offending field. What is specific to a higher-order provider is that the *rest* of the diagnostic differs by layer, so the two cases are told apart by shape rather than by the leaf alone. + +When the **inner** layer fails, the compiler prints **two** `E0277` blocks. The first is an intermediate block reporting that the inner provider does not implement its provider trait for the concrete context — `BaseArea: AreaCalculator` is not satisfied — carrying the [near-contradiction](../../guides/debugging.md) hint that `AreaCalculator<__Context__>` *is* implemented for `BaseArea` (the generic impl exists but is inapplicable here). The second block is the real one: `Rectangle: CanUseComponent` is not satisfied, its `help:` naming the missing `HasField`, its `unsatisfied trait bound introduced here` caret landing on the *inner* provider's `Self: HasBaseArea` clause, and its `required for …` chain running through `BaseArea`'s `IsProviderFor` and then — after a `1 redundant requirement hidden` note — through `ScaledArea`'s `IsProviderFor` to `CanUseComponent`. The chain passing through both providers' `IsProviderFor`, and the extra intermediate block, are the marks of an inner-layer failure. + +When the **outer** layer fails, the compiler prints a **single**, shorter `E0277` block. It is the `Rectangle: CanUseComponent` block, its `help:` naming the missing `HasField`, its caret landing on the *outer* provider's `Self: HasScaleFactor` clause, and its chain running from `ScaledArea`'s `IsProviderFor` straight to `CanUseComponent` — with no `redundant requirement` note and no mention of the inner `BaseArea` at all. The outer layer fails before it ever delegates inward, so the inner provider never enters the proof. + +## Where the root cause is + +The root cause is **present** in the `help:` note of the `CanUseComponent` block in both cases, and the failing *layer* is present too, in two reinforcing places. The `unsatisfied trait bound introduced here` caret sits on the `where` clause of the provider whose dependency is unmet — the inner provider for an inner failure, the outer provider for an outer one — which is the most direct signal. The chain depth confirms it: an inner failure runs through *both* providers' `IsProviderFor` and carries the `1 redundant requirement hidden` note, while an outer failure stops at the outer provider's `IsProviderFor` and never names the inner one. The intermediate `Inner: ProviderTrait` block that appears only for the inner case is a third, weaker signal — its presence points inward, but it is also the noise a reader should skip past to reach the `CanUseComponent` block that carries the leaf. + +## Resolving it + +Fix the dependency at the layer the diagnostic points to. For the inner failure, supply the field the inner provider needs — add `base_area` to `Rectangle` so `BaseArea` implements `HasBaseArea`; for the outer failure, supply the field the outer provider needs — add `scale_factor` so `ScaledArea` implements `HasScaleFactor`. Wiring is unaffected: the stack `ScaledArea` is correct in both cases, and the fix is always to satisfy the named leaf, never to change the delegation. + +When a stack is deep enough that reading the chain is awkward, force the layer boundary explicitly with the `#[check_providers(...)]` form of [`check_components!`](../../reference/macros/check_components.md). It changes the assertion from `CanUseComponent` on the context to `IsProviderFor` on each named provider, so a dependency missing only from the outer wrapper errors on that provider's line alone while one missing from the inner provider errors on both — pinpointing the layer without decoding the chain. The [debugging guide](../../guides/debugging.md) prescribes this move; this document is the anatomy behind it. + +## Notes for tooling + +For a `cargo-cgp`-style post-processor, the headline is again the leaf in the `CanUseComponent` block's `help:` note, but this class adds a second fact worth reporting: **the provider layer at fault**, recoverable as the provider named in the innermost `IsProviderFor` note whose `where` clause the `introduced here` caret sits on. A tool should present the two together — "`Rectangle` is missing field `base_area`, needed by the inner provider `BaseArea` (wrapped by `ScaledArea`)" — reconstructing the wrapper relationship from the `IsProviderFor` chain rather than dumping it. The intermediate `BaseArea: AreaCalculator` near-contradiction block should be suppressed as derived noise, exactly as in a [verbose cascade](verbose-cascade.md); it names only a provider trait and never reaches the field. Emulating `#[check_providers(...)]` — re-asserting `IsProviderFor` per layer — is the internal move a tool can make to localize the layer mechanically instead of parsing the chain. + +## Backing fixtures + +- [acceptable/check_components/higher_order_inner_dependency.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_inner_dependency.rs) — `ScaledArea` wired onto a `Rectangle` with `scale_factor` but no `base_area`, so the inner layer fails; its `.stderr` pins the two-block shape, the `HasField` leaf, the caret on `BaseArea`'s `Self: HasBaseArea`, and the chain through both providers' `IsProviderFor` with the `1 redundant requirement hidden` note. +- [acceptable/check_components/higher_order_outer_dependency.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_outer_dependency.rs) — the mirror, with `base_area` but no `scale_factor`, so the outer layer fails; its `.stderr` pins the single, shorter block, the `HasField` leaf, the caret on `ScaledArea`'s `Self: HasScaleFactor`, and a chain that stops at the outer `IsProviderFor` without naming the inner provider. + +## Related + +- [Check-trait failure (surfaced)](check-trait-failure.md) — the single-layer form of the same surfaced diagnostic; this class is what it looks like when the failing provider wraps another. +- [Verbose dependency cascade](verbose-cascade.md) — the sibling class whose intermediate provider-trait blocks are the same noise this class also emits for an inner failure. +- [`check_components!`](../../reference/macros/check_components.md) and its `#[check_providers(...)]` form — the macro this class is expressed through and the tool that localizes the layer by hand. +- [`IsProviderFor`](../../reference/traits/is_provider_for.md) and [`#[use_provider]`](../../reference/attributes/use_provider.md) — the supertrait that carries each layer's `where` clause into the chain, and the attribute that completes the inner provider's bound. +- [Higher-order providers](../../concepts/higher-order-providers.md) and [Debugging CGP compile errors](../../guides/debugging.md) — the concept this class fails in, and the playbook that prescribes `#[check_providers]`. diff --git a/docs/errors/checks/verbose-cascade.md b/docs/errors/checks/verbose-cascade.md index 7186a414..65bb655a 100644 --- a/docs/errors/checks/verbose-cascade.md +++ b/docs/errors/checks/verbose-cascade.md @@ -57,6 +57,7 @@ For a `cargo-cgp`-style post-processor, this class is the strongest case for **d ## Related - [Check-trait failure (surfaced)](check-trait-failure.md) — the single-cause diagnostic this class multiplies. +- [Higher-order provider layer failure (surfaced)](higher-order-provider-layer.md) — a sibling class that emits the same intermediate provider-trait noise blocks, there to distinguish an inner-layer failure from an outer one. - [Unsatisfied dependency (hidden)](../hidden/unsatisfied-dependency.md) — the same root cause seen through consumer-trait calls, where each block hides the cause instead of surfacing it. - [Debugging CGP compile errors](../../guides/debugging.md) — why the error count tells you nothing about the number of mistakes, and how to isolate the one link. - [`check_components!`](../../reference/macros/check_components.md) and [`IsProviderFor`](../../reference/traits/is_provider_for.md). diff --git a/docs/errors/error_codes/e0277.md b/docs/errors/error_codes/e0277.md index 485090c4..b3b773df 100644 --- a/docs/errors/error_codes/e0277.md +++ b/docs/errors/error_codes/e0277.md @@ -20,6 +20,7 @@ One special case is worth recognizing because its message does not match the gen - [Unsatisfied ordinary trait bound](../checks/ordinary-trait-bound.md) — a check surfacing an unmet *ordinary* trait (`f64: Eq`) as the primary error. - [Unregistered namespace path](../checks/unregistered-namespace-path.md) — a namespace redirect landing on a path no entry binds, reported as an unsatisfied `DefaultNamespace`/`DelegateComponent` lookup. - [Verbose dependency cascade](../checks/verbose-cascade.md) — the same surfaced `E0277` multiplied across transitively dependent providers. +- [Higher-order provider layer failure](../checks/higher-order-provider-layer.md) — the same surfaced `E0277` where the chain shape identifies which layer of a wrapped provider carries the unmet leaf. - [Ill-formed generated type](../lowering/ill-formed-generated-type.md) — the `Sized` special case, from a macro lowering a shorthand into an unsized type. - [Unsatisfied dependency (hidden)](../hidden/unsatisfied-dependency.md) — the `E0277` variant when a consumer trait is used as a `where` bound rather than called. diff --git a/docs/guides/debugging.md b/docs/guides/debugging.md index a1568095..921cd733 100644 --- a/docs/guides/debugging.md +++ b/docs/guides/debugging.md @@ -56,7 +56,7 @@ Spawn a sub-agent instead of grepping when the search will not converge. Grep is Because a lazy failure surfaces far from its cause, the most reliable first move is to force the check *at the wiring site* with [`check_components!`](../reference/macros/check_components.md). A standalone check trait asserts `CanUseComponent` for each component you name, so a missing dependency errors on the checked component's line — walking through `IsProviderFor` to name the real cause — instead of at some distant call site. Adding a check for the component you suspect turns a confusing downstream error into one anchored at the wiring you control. The [check-traits concept](../concepts/check-traits.md) explains why this works. -For a [higher-order provider](../concepts/higher-order-providers.md) — a provider wrapping another provider — a plain check tells you the stack is broken but not which layer. The `#[check_providers(...)]` attribute changes the assertion from `CanUseComponent` on the context to `IsProviderFor` on each named provider, so a dependency missing only from the outer wrapper errors on its line alone while one missing from the inner provider errors on both, pinpointing the layer. When a component has generic parameters, check it with concrete ones (`FooComponent: (Rectangle, f64)`) so the check actually instantiates the wiring you doubt rather than leaving it generic and unchecked. +For a [higher-order provider](../concepts/higher-order-providers.md) — a provider wrapping another provider — a plain check tells you the stack is broken but not which layer, though its diagnostic shape already leans one way: an inner-layer failure runs the `required for …` chain through both providers' `IsProviderFor` (with a `redundant requirement hidden` note), while an outer-layer failure stops at the outer provider and never names the inner one, as the [higher-order provider layer failure](../errors/checks/higher-order-provider-layer.md) class details. To force the boundary rather than read it, the `#[check_providers(...)]` attribute changes the assertion from `CanUseComponent` on the context to `IsProviderFor` on each named provider, so a dependency missing only from the outer wrapper errors on its line alone while one missing from the inner provider errors on both, pinpointing the layer. When a component has generic parameters, check it with concrete ones (`FooComponent: (Rectangle, f64)`) so the check actually instantiates the wiring you doubt rather than leaving it generic and unchecked. ## Reduce the failure to a minimal reproduction From 8c649cce57c2028f9f3e54ac5f438373d1c721d7 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Fri, 10 Jul 2026 23:11:33 +0200 Subject: [PATCH 2/5] Remove `#[diagnostic::on_unimplemented]` and delegate diagnostics to cargo-cgp --- crates/core/cgp-component/src/traits/delegate_component.rs | 4 ---- crates/core/cgp-component/src/traits/is_provider.rs | 3 --- crates/core/cgp-field/src/traits/has_field.rs | 4 ---- crates/core/cgp-field/src/traits/has_field_mut.rs | 4 ---- 4 files changed, 15 deletions(-) diff --git a/crates/core/cgp-component/src/traits/delegate_component.rs b/crates/core/cgp-component/src/traits/delegate_component.rs index fca84a03..446d2c54 100644 --- a/crates/core/cgp-component/src/traits/delegate_component.rs +++ b/crates/core/cgp-component/src/traits/delegate_component.rs @@ -39,10 +39,6 @@ } ``` */ -#[diagnostic::on_unimplemented( - message = "{Self} does not contain any DelegateComponent entry for {Key}", - note = "You might want to implement the provider trait for {Key} on {Self}" -)] pub trait DelegateComponent { type Delegate; } diff --git a/crates/core/cgp-component/src/traits/is_provider.rs b/crates/core/cgp-component/src/traits/is_provider.rs index 12d149fa..b1e0a5e9 100644 --- a/crates/core/cgp-component/src/traits/is_provider.rs +++ b/crates/core/cgp-component/src/traits/is_provider.rs @@ -117,7 +117,4 @@ Rust would follow the implementation path and surface all unsatisfied constraints from `GetFooValue`. */ -#[diagnostic::on_unimplemented( - note = "You need to add `#[cgp_provider({Component})]` on the impl block for CGP provider traits" -)] pub trait IsProviderFor {} diff --git a/crates/core/cgp-field/src/traits/has_field.rs b/crates/core/cgp-field/src/traits/has_field.rs index fb966a08..2aa3c900 100644 --- a/crates/core/cgp-field/src/traits/has_field.rs +++ b/crates/core/cgp-field/src/traits/has_field.rs @@ -43,10 +43,6 @@ use cgp_component::UseContext; } ``` */ -#[diagnostic::on_unimplemented( - message = "HasField is not implemented for {Self} with the field: {Tag}", - note = "You need to add #[derive(HasField)] to {Self} with the given field present in the struct" -)] pub trait HasField { type Value; diff --git a/crates/core/cgp-field/src/traits/has_field_mut.rs b/crates/core/cgp-field/src/traits/has_field_mut.rs index e47f393c..56f76c99 100644 --- a/crates/core/cgp-field/src/traits/has_field_mut.rs +++ b/crates/core/cgp-field/src/traits/has_field_mut.rs @@ -3,10 +3,6 @@ use core::ops::DerefMut; use crate::traits::{FieldGetter, HasField}; -#[diagnostic::on_unimplemented( - message = "HasFieldMut is not implemented for {Self} with the field: {Tag}", - note = "You need to add #[derive(HasField)] to {Self} with the given field present in the struct" -)] pub trait HasFieldMut: HasField { fn get_field_mut(&mut self, tag: PhantomData) -> &mut Self::Value; } From 805a3d6c357b28a6db789a7579a8d7a188235bcf Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Mon, 20 Jul 2026 21:27:35 +0200 Subject: [PATCH 3/5] docs: adopt new tag line and propagate it across READMEs and communication strategy Retire the incumbent "a modular programming paradigm for Rust" in favor of the chosen "a language extension for Rust, with pluggable trait implementations at compile-time." - Rewrite communication-strategy/tag-lines.md around the single chosen line: word-by-word analysis, the incumbent kept only as historical background, the post-tag-line pitch funnel, and model introductions for a new audience. - Lead cgp/README.md and docs/README.md with the new tag line and introduction. - Propagate the line through selling-points, formats, worked-examples, and vocabulary (recording "pluggable" as the lead descriptor and noting that "reusable" undersells the novelty to a Rust reader), and fix the now-stale "tag-line shortlist" reference in attention-and-engagement. - Update the communication-strategy README catalog entry to match. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 19 +++- docs/README.md | 4 +- docs/communication-strategy/README.md | 2 +- .../attention-and-engagement.md | 2 +- docs/communication-strategy/formats.md | 4 +- docs/communication-strategy/selling-points.md | 2 +- docs/communication-strategy/tag-lines.md | 92 +++++++++++-------- docs/communication-strategy/vocabulary.md | 2 + .../communication-strategy/worked-examples.md | 12 +-- 9 files changed, 83 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index c9a5772a..0e61f54e 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ ![Rust Stable](https://img.shields.io/badge/rustc-stable-blue.svg) ![Rust 1.89+](https://img.shields.io/badge/rustc-1.89+-blue.svg) -**A Rust language extension for reusable, swappable trait implementations — checked at compile time, with zero runtime cost.** +**A language extension for Rust, with pluggable trait implementations at compile-time.** -Context-generic programming (CGP) is a library, built on stable Rust, that lets one interface have many interchangeable implementations and lets each *context* — an application, a test, a deployment — choose which one it uses. The choice is written in one readable place and resolved entirely during compilation, so it compiles down to direct calls: there is no runtime container, no reflection, and nothing left in the binary for an implementation a context does not use. +Context-Generic Programming (CGP) is a library on stable Rust that lifts the language's one-implementation-per-type limit off your traits: it lets one interface have many interchangeable implementations and lets each *context* — an application, a test, a deployment — plug in the one it uses. The choice is written in one readable place and resolved entirely during compilation, so it compiles down to direct calls: there is no runtime container, no reflection, and nothing left in the binary for an implementation a context does not use. > [!IMPORTANT] > The `main` branch tracks the upcoming **v0.8.0** release (published as `0.8.0-alpha` pre-release crates). The current stable release on crates.io is **v0.7.0**, which is **not compatible** with this branch — v0.8.0 changes and removes syntax that v0.7.0 used. **All documentation in this repository describes v0.8.0 only.** For v0.7.0, refer to its published crate documentation instead. @@ -72,8 +72,19 @@ impl StorageObjectFetcher { /* fetch the object from Amazon S3 */ } impl StorageObjectFetcher { /* fetch the object from Google Cloud Storage */ } // ...and each context picks one, resolved at compile time. -delegate_components! { App { StorageObjectFetcherComponent: FetchS3Object } } -delegate_components! { GCloudApp { StorageObjectFetcherComponent: FetchGCloudObject } } +delegate_components! { + App { + StorageObjectFetcherComponent: FetchS3Object, + // ... + } +} + +delegate_components! { + GCloudApp { + StorageObjectFetcherComponent: FetchGCloudObject, + // ... + } +} ``` `App` fetches from Amazon S3, and `GCloudApp` — a context carrying a Google Cloud storage client instead — fetches from Google Cloud. Neither pays for `dyn` or runtime dispatch, the choice is a single greppable line, and code that calls `self.fetch_storage_object(..)` never changes. (Method bodies are elided here for brevity.) diff --git a/docs/README.md b/docs/README.md index 24cf1dc5..9b03eabf 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,8 @@ # CGP Knowledge Base -This directory is a knowledge base about Context-Generic Programming (CGP), written by and for AI coding agents. Its purpose is to give an agent everything it needs to understand the *full semantics* of CGP — what each construct means, what code it expands into, and how the pieces fit together — without having to re-derive that understanding from the macro implementation every time. The `/cgp` skill gives a fast orientation; this knowledge base is the durable, version-controlled record that goes deeper and stays in sync with the code. +Context-Generic Programming (CGP) is a language extension for Rust, with pluggable trait implementations at compile-time. In ordinary Rust a trait has one implementation per type; CGP lets one trait have many interchangeable implementations and lets each context choose which one it uses, through a small wiring table the compiler resolves statically — so the flexibility costs nothing at runtime. It is an ordinary library on stable Rust that desugars to plain traits and impls, adopted one component at a time, and it reaches beyond swappable implementations to abstract types each context picks for itself, extensible records and variants, and a family of composable handlers. + +This directory is a knowledge base about CGP, written by and for AI coding agents. Its purpose is to give an agent everything it needs to understand the *full semantics* of CGP — what each construct means, what code it expands into, and how the pieces fit together — without having to re-derive that understanding from the macro implementation every time. The `/cgp` skill gives a fast orientation; this knowledge base is the durable, version-controlled record that goes deeper and stays in sync with the code. ## Why this exists diff --git a/docs/communication-strategy/README.md b/docs/communication-strategy/README.md index 7e257f8d..a9369dea 100644 --- a/docs/communication-strategy/README.md +++ b/docs/communication-strategy/README.md @@ -59,7 +59,7 @@ The documents below inform how CGP is presented to the public; register a new on - [Reader profiles](reader-profiles.md) — the kinds of readers public-facing CGP writing must serve, from newcomers to Rust through advanced developers and across the backgrounds they arrive from, with what each reader already knows, is excited by, grows skeptical of, and needs from a piece written for them. - [Selling points](selling-points.md) — the true capabilities CGP should advertise, each with the phrasings that make it land and the phrasings that backfire, plus audience-tuned one-liners keyed to the reader profiles and the related-work comparisons. - [Skepticism](skepticism.md) — the objections a reader brings to CGP, whether imported from a paradigm they distrust or native to the Rust community, judged for whether they are justified and answered with wording that convinces without triggering the misunderstanding that fed them. -- [Tag lines](tag-lines.md) — a brainstorm of the candidate one-line descriptions of CGP, from the incumbent "modular programming paradigm" through the "language extension" and feature-first framings, each weighed for attention, skepticism, and honest feasibility, with a recommended shortlist to validate. +- [Tag lines](tag-lines.md) — CGP's chosen one-line description — "a language extension for Rust, with pluggable trait implementations at compile-time" — analyzed word by word against attention, skepticism, and honest feasibility, with the retired "modular programming paradigm" kept as historical background, the high-level pitch that should follow the line, and model introductions for a new audience. - [Key features](key-features.md) — the short, curated headline set of the few best selling points to put on the front page, with titles and one-line copy chosen for breadth, honesty, and the phrasing lessons of the rest of the section. - [Technical barriers](technical-barriers.md) — the comprehension barriers a reader hits when learning CGP, from unfamiliarity with generics and traits upward, and the design affordances and progressive-disclosure teaching moves that lower each one. - [Attention and engagement](attention-and-engagement.md) — the evidence base for where the Rust community's attention actually sits, what CGP's own public reception has been, and which conversations and channels a piece should attach to; the engagement counterpart to related-work and the home of the section's external citations. diff --git a/docs/communication-strategy/attention-and-engagement.md b/docs/communication-strategy/attention-and-engagement.md index 9460c2ba..741aa755 100644 --- a/docs/communication-strategy/attention-and-engagement.md +++ b/docs/communication-strategy/attention-and-engagement.md @@ -51,4 +51,4 @@ Attention is channel-specific, so the hook should be chosen for where a piece wi ## Reading the reaction -Because attention is empirical, the real grade of any hook is the reaction it draws, so the section should treat publication as a measurement rather than a conclusion. A hook is working when it draws questions about *how* CGP achieves something; it is failing when the top replies are the dismissals this audience actually reaches for — the ones observed in CGP's own threads above: "verbose / over-engineered," "I can't tell what code runs," "isn't this just AOP / COM / ML modules reinvented," and "the name tells me nothing," alongside the imported "just macros" and "another DI framework." Those replies are not noise but signal that the framing let the reflex fire before the novel part landed, and the fix is upstream, in the opening lines, per [reader-profiles.md](reader-profiles.md) and [tag-lines.md](tag-lines.md). Float a candidate hook where the target profile actually gathers, watch which dismissal it attracts, and revise toward the framing that draws the *how* question instead — the tag-line shortlist in [tag-lines.md](tag-lines.md) is offered for exactly this validation. +Because attention is empirical, the real grade of any hook is the reaction it draws, so the section should treat publication as a measurement rather than a conclusion. A hook is working when it draws questions about *how* CGP achieves something; it is failing when the top replies are the dismissals this audience actually reaches for — the ones observed in CGP's own threads above: "verbose / over-engineered," "I can't tell what code runs," "isn't this just AOP / COM / ML modules reinvented," and "the name tells me nothing," alongside the imported "just macros" and "another DI framework." Those replies are not noise but signal that the framing let the reflex fire before the novel part landed, and the fix is upstream, in the opening lines, per [reader-profiles.md](reader-profiles.md) and [tag-lines.md](tag-lines.md). Float a candidate hook where the target profile actually gathers, watch which dismissal it attracts, and revise toward the framing that draws the *how* question instead — the chosen tag line in [tag-lines.md](tag-lines.md) is offered for exactly this validation. diff --git a/docs/communication-strategy/formats.md b/docs/communication-strategy/formats.md index 09b92cad..39890c8a 100644 --- a/docs/communication-strategy/formats.md +++ b/docs/communication-strategy/formats.md @@ -10,7 +10,7 @@ Every playbook below shares four moving parts, and naming them once keeps each e ## The link-aggregator launch post (Lobsters and r/rust) -This is where CGP is actually discussed, so it is the format to get right first, and its reader is the pragmatic skimmer who will form and broadcast a snap judgment. Title it with a concrete capability, not the paradigm name — "reusable, swappable trait implementations for Rust" over "context-generic programming" — because the title is the whole pitch for most of the audience, per [tag-lines.md](tag-lines.md). Open the body on a runnable `Hello World` or a five-line before/after from [problems-solved.md](problems-solved.md); the one piece of feedback CGP's own launch received asked for exactly this ([attention-and-engagement.md](attention-and-engagement.md)), and it is what the format rewards. Keep it short and let the linked material carry the depth. Preempt the two dismissals this audience fires fastest — "verbose / over-engineered" and "why not just traits" — in the first paragraph, by naming the concrete pain that justifies the machinery and conceding where a plain trait would suffice, per [positioning.md](positioning.md). Then be present in the thread: this channel expects the author to answer, and the canned responses below are what to keep ready. The call to action is the quickstart, not the full paradigm. +This is where CGP is actually discussed, so it is the format to get right first, and its reader is the pragmatic skimmer who will form and broadcast a snap judgment. Title it with the concrete-capability half of the tag line, not the paradigm name — "pluggable trait implementations for Rust, at compile-time" over "context-generic programming" — because the title is the whole pitch for most of the audience, per [tag-lines.md](tag-lines.md). Open the body on a runnable `Hello World` or a five-line before/after from [problems-solved.md](problems-solved.md); the one piece of feedback CGP's own launch received asked for exactly this ([attention-and-engagement.md](attention-and-engagement.md)), and it is what the format rewards. Keep it short and let the linked material carry the depth. Preempt the two dismissals this audience fires fastest — "verbose / over-engineered" and "why not just traits" — in the first paragraph, by naming the concrete pain that justifies the machinery and conceding where a plain trait would suffice, per [positioning.md](positioning.md). Then be present in the thread: this channel expects the author to answer, and the canned responses below are what to keep ready. The call to action is the quickstart, not the full paradigm. ## The blog tutorial or deep-dive @@ -18,7 +18,7 @@ A tutorial has the reader's sustained attention but must earn each step, so it l ## The README and landing page -The README's job is the first screen, where an evaluator and a skimmer both decide in seconds whether to continue. Put the layered tag line at the top — the owned name paired with a plain descriptor and a subline that heads off the runtime-container and new-language misreadings, per [tag-lines.md](tag-lines.md) — then the small headline set from [key-features.md](key-features.md), then a runnable `Hello World` above the fold (the first screen, before the reader scrolls), because a reader wants to see the code before the prose. Keep the feature set to the ruthless few; a list of ten reads as a list of none. State "a library on stable Rust" and the install line early, since the [evaluator](reader-profiles.md) is scanning for the toolchain gamble. The call to action is the quickstart for the skimmer and a link to the honest maturity-and-adoption discussion for the evaluator. +The README's job is the first screen, where an evaluator and a skimmer both decide in seconds whether to continue. Put the tag line at the top as the descriptor beneath the project name — "a language extension for Rust, with pluggable trait implementations at compile-time" — with a reassurance line under it that heads off the runtime-container and new-language misreadings, per [tag-lines.md](tag-lines.md) — then the small headline set from [key-features.md](key-features.md), then a runnable `Hello World` above the fold (the first screen, before the reader scrolls), because a reader wants to see the code before the prose. Keep the feature set to the ruthless few; a list of ten reads as a list of none. State "a library on stable Rust" and the install line early, since the [evaluator](reader-profiles.md) is scanning for the toolchain gamble. The call to action is the quickstart for the skimmer and a link to the honest maturity-and-adoption discussion for the evaluator. ## The conference talk or video diff --git a/docs/communication-strategy/selling-points.md b/docs/communication-strategy/selling-points.md index 4a9b1716..75350563 100644 --- a/docs/communication-strategy/selling-points.md +++ b/docs/communication-strategy/selling-points.md @@ -10,7 +10,7 @@ Two habits make the difference between a pitch that lands and one that invites a ## The headline pitch -CGP's one-line identity should say what it *is* and what it *costs* in a single breath, because the reader's first question is "what is this" and their immediate second is "what does it cost me." The value proposition that does both is that **CGP is modular programming for Rust: write many interchangeable implementations of an interface and choose between them per context, resolved entirely at compile time with zero runtime cost.** That sentence leads with the benefit (many implementations, chosen per context), names the mechanism honestly (it is a way of writing Rust, not a runtime), and closes the escape hatch a skeptic reaches for (no runtime cost). Shorter openers — "swappable implementations, wired per context, erased before it runs" — keep the same three beats. +CGP's one-line identity should say what it *is* and what it *costs* in a single breath, because the reader's first question is "what is this" and their immediate second is "what does it cost me." The value proposition that does both is that **CGP is a language extension for Rust, with pluggable trait implementations at compile-time: write many interchangeable implementations of an interface and choose between them per context, resolved entirely at compile time with zero runtime cost.** That sentence leads with the benefit (pluggable implementations, chosen per context), names the mechanism honestly (an extension that compiles to ordinary Rust, not a runtime), and closes the escape hatch a skeptic reaches for (no runtime cost). Shorter openers — "swappable implementations, wired per context, erased before it runs" — keep the same three beats. Resist the urge to lead with the machinery. Opening on the consumer/provider trait split, `DelegateComponent`, or coherence-bypassing loses every reader but the advanced enthusiast, and even they respond better to a problem first. The internals are what a curious reader graduates *into*, not what they should meet in the first sentence. Reserve the phrase "context-generic programming" for after the value has landed; as an opener it is a name the reader cannot yet decode, and a name is not a pitch. diff --git a/docs/communication-strategy/tag-lines.md b/docs/communication-strategy/tag-lines.md index 0818827c..dc301bbd 100644 --- a/docs/communication-strategy/tag-lines.md +++ b/docs/communication-strategy/tag-lines.md @@ -1,77 +1,89 @@ -# Tag lines +# Tag line -This document brainstorms the tag lines CGP could use to describe itself to the general public, weighing each for the attention it wins, the skepticism it triggers, and whether the claim is one CGP can honestly defend. +This document records CGP's chosen tag line, the analysis that justifies every word of it, the high-level pitch that should follow it, and model introductions that put the two to work for a new audience. -## Why the tag line is worth this much thought +## The chosen tag line -The tag line is the first thing a reader learns about CGP and often the only thing, so it shapes the project's reception more than any document beneath it. It is the compressed form of everything in [selling-points.md](selling-points.md) and [skepticism.md](skepticism.md): in a handful of words it has to gain attention and minimize misunderstanding at once, for an audience — the [reader profiles](reader-profiles.md) — that ranges from a skimmer giving it three seconds to an expert who will pick the wording apart. A tag line that wins the skimmer but reads as an overclaim to the expert has failed, and so has one that satisfies the expert but says nothing a skimmer can act on. +CGP's front-page tag line is: -The incumbent tag line — CGP as "a modular programming paradigm for Rust" — has carried the project since its early days, but it now underperforms for two separable reasons. First, it undersells what CGP has become. The project began as a way to make trait implementations modular, but it has since accreted abstract types, extensible data, a handler and computation family, namespaces, and more, until it reads less like one technique and more like a broad extension to Rust itself. Second, and more damaging, its key word does not land. "Modularity" is an abstraction few developers wake up wanting, "paradigm" reads as academic, and for a large slice of the audience "modular" carries active baggage from runtime dependency-injection frameworks — the hidden dependencies, the configuration weight, the "magic" documented in [dependency injection](../related-work/dependency-injection.md) — so the word meant to attract instead signals over-engineering to the very pragmatists CGP most needs to win, the reflex catalogued in [skepticism.md](skepticism.md). A tag line whose lead word repels part of the audience on contact is working against the project. +> **A language extension for Rust, with pluggable trait implementations at compile-time.** -This document therefore treats the tag line as an open question. It sets out how to judge a candidate, works through the field honestly — including the "language of its own" alternative the project is considering — and lands on a recommended direction to validate rather than a decree, because tag-line efficacy is ultimately empirical. +This is the settled lead descriptor for landing pages, READMEs, talks, and posts — the one line to open with when a reader meets CGP for the first time, and the phrasing every other piece of public writing should echo rather than reinvent. It is chosen, not merely proposed, but its real grade is still the reaction it draws: a tag line's efficacy is empirical, and publication is the measurement, so treat the line as fixed for consistency's sake while watching which questions and dismissals it attracts in the channels where the [reader profiles](reader-profiles.md) gather, per [attention-and-engagement.md](attention-and-engagement.md). The rest of this document explains why this line was chosen, what to say immediately after it, and how to weave the two into an opening a newcomer can act on. -## How to judge a tag line +## Why the tag line carries this much weight -A candidate is judged on three axes, and the third is the one that most often kills an otherwise attractive line. The first is **attention**: does it hook, and whom — a tag line that excites the type-system enthusiast may leave the pragmatist cold, and vice versa. The second is **skepticism**: what aversion or misunderstanding it triggers, and whom it repels — the loaded word, the overclaim, the frame that invites a dismissal. The third is **feasibility**, meaning honesty: is the claim accurate and defensible to the audience most able to check it, because the honesty-is-the-strategy rule of this section means a tag line that overclaims does more damage than a modest one, since the readers worth winning are the ones who will catch it. +The tag line shapes CGP's reception more than any document beneath it, because it is the first thing a reader learns and often the only thing. In a handful of words it has to do two jobs at once — gain attention and prevent misunderstanding — for an audience that ranges from a skimmer giving it three seconds to an expert who will pick the wording apart. It is the compressed form of everything in [selling-points.md](selling-points.md) and [skepticism.md](skepticism.md): a line that wins the skimmer but reads as an overclaim to the expert has failed, and so has one that satisfies the expert but gives the skimmer nothing to act on. That double burden is why the exact words are worth this much scrutiny, and why the analysis below weighs each one. -Three further rules break ties between candidates that score similarly. A **name is not a pitch**: a coined term tells a cold reader nothing, so it must be paired with a plain-language descriptor rather than shipped alone. A tag line can be **layered**: a short hook carries the attention and a subline carries the honest mechanism, so the two jobs need not fight for the same words. And the decisive test is that a tag line is **checked against the skeptic and the skimmer first**, because they are the least forgiving and most numerous readers in [reader-profiles.md](reader-profiles.md), and a line that survives them survives everyone. +## How a tag line is judged -## The candidates +A candidate line is judged on three axes, and the third most often kills an otherwise attractive one. The first is **attention**: does it hook, and whom — a line that excites the type-system enthusiast may leave the pragmatist cold. The second is **skepticism**: what aversion or misreading it triggers, and whom it repels — the loaded word, the overclaim, the frame that invites a dismissal. The third is **feasibility**, meaning honesty: is the claim accurate and defensible to the audience most able to check it, because the honesty-is-the-strategy rule of this section means an overclaiming line does more damage than a modest one, since the readers worth winning are exactly the ones who will catch it. -The field below runs from the most conservative framing to the most ambitious, with the feature- and benefit-first options and the borrowed analogy in between. Each is weighed on the three axes and given a verdict. +Three further rules break ties and shape how the line is deployed. A **name is not a pitch**: a coined term tells a cold reader nothing, so it is paired with a plain-language descriptor rather than shipped alone. A tag line can be **layered**: a short lead carries the attention and a subline carries the honest mechanism, so the two jobs need not fight for the same words. And the decisive test is that a line is **checked against the skeptic and the skimmer first**, because they are the least forgiving and most numerous readers in [reader-profiles.md](reader-profiles.md), and a line that survives them survives everyone. -### The incumbent — "a modular programming paradigm for Rust" +## Historical background — "a modular programming paradigm for Rust" -This line is accurate and safe, but the weakest on attention, and its lead word actively repels part of the audience. Its selling point is honesty and humility: it describes CGP as a way of writing rather than a runtime, and it resonates with the architecture-minded and dependency-injection-experienced reader who already values decoupling. Its skepticism cost is the one the project has already felt — "modularity" is abstract and, as the documented Spring sentiment shows, unloved or actively distrusted by developers who associate it with over-engineering, while "paradigm" reads as academic and conveys neither novelty nor a concrete pain. On feasibility it is unimpeachable: it overclaims nothing. The verdict is that it is *true but inert* — the safest possible descriptor and therefore worth keeping as a fallback, but the wrong thing to lead with, because its hook word is the one most likely to lose the pragmatist and the skimmer. +The chosen line replaces a long-serving predecessor, and understanding why that one is retired is the clearest way to see what the new one is built to fix. For most of the project's life CGP described itself as **"a modular programming paradigm for Rust."** That line was honest and safe, and it had genuine virtues: it described CGP as a way of *writing* rather than a runtime, it overclaimed nothing, and it resonated with the architecture-minded reader who already values decoupling. As a fallback it is still unimpeachable on the feasibility axis. -### The bold alternative — "a language of its own, a superset of Rust" +It underperformed on the other two axes for two separable reasons, and both carry a lesson the new line answers. First, it **undersold what CGP had become**. The project began as a way to make trait implementations modular, but it has since grown abstract types, extensible data, a handler and computation family, namespaces, and more, until it reads less like one technique and more like a broad extension to the language — a breadth "a paradigm" quietly hides. Second, and more damaging, its **lead word repelled part of the audience on contact**. "Modularity" is an abstraction few developers wake up wanting, "paradigm" reads as academic, and for a large slice of the audience "modular" carries active baggage from runtime dependency-injection frameworks — the hidden dependencies, the configuration weight, the "magic" documented in [dependency injection](../related-work/dependency-injection.md) — so the word meant to attract instead signaled over-engineering to the very pragmatists CGP most needs to win, the reflex catalogued in [skepticism.md](skepticism.md). -This is the ambitious reframing the project is weighing, and it splits sharply across the three axes. Its selling point is real: "superset of Rust" is intriguing enough to stop a skimmer, it honestly reflects how much CGP now adds, and it has a beloved precedent in TypeScript, whose "a superset that compiles down and adds an expressive type layer" positioning became one of the most successful in modern tooling. Under the TypeScript reading it can even *support* gradual adoption — "all your Rust still works, plus more." +The standing lesson is therefore to **retire "modular" as a lead word**. It is the weakest hook of any CGP has tried and the only one that actively repels, so modularity survives only as a supporting descriptor, and when it is used it must be immediately differentiated from its framework connotations by the three properties that distinguish CGP from a runtime container — "compile-time," "zero-cost," and "explicit." The chosen line puts that lesson into practice: it keeps the honest "a way of writing Rust" spirit of the incumbent while leading with words that pull the audience in rather than push part of it away. -The skepticism cost is serious and comes in two parts. First, "a language of its own" scares the evaluator: it imports the full adoption-risk fear from [skepticism.md](skepticism.md) — a language to learn, tooling, ecosystem, hiring, lock-in — and reads as "throw away what you know," which fights CGP's strongest reassurance that it is a superset of ordinary traits adopted a little at a time. Second, the framing flips on which reading the reader takes: "superset" TypeScript-style (your code still works, plus more) is inviting, while "a language of its own" pushes the opposite, all-or-nothing reading, and the phrase invites the harsher one. +## Why the chosen line works, word by word -Feasibility is where this candidate is weakest, and the point is concrete. CGP is a set of procedural macros and libraries that desugar to ordinary Rust and compile on a stable toolchain; it adds no grammar to the language. Taken literally, then, "a language of its own" and "a superset of Rust" are inaccurate, and the precise, pedantic slice of the Rust audience — a real and vocal group — will answer "it's not a language, it's a macro library," and a tag line that invites that correction has lost the exchange before it begins. The verdict is that the *ambition* is right and worth capturing, but the *words* overclaim; the honest carrier of the same ambition is "extension," below. +The chosen line was assembled so that each part does one job the others cannot, and reading it word by word shows why no piece can be dropped. Taken together, "a language extension for Rust, with pluggable trait implementations at compile-time" carries breadth, a concrete hook, and a misreading-defuser in a single sentence — the three things the analysis of every weaker option showed a tag line has to hold at once. -### The honest upgrade — "a language extension for Rust" +**"A language extension for Rust"** captures the ambition honestly. "Extension" signals that CGP significantly enlarges what the language can express — matching the breadth the incumbent hid — while implying *addition to* rather than *replacement of*, so it supports gradual adoption instead of fighting it. It is also the defensible version of a bolder framing that was rejected: calling CGP "a language of its own" or "a superset of Rust" overclaims, because CGP is a set of procedural macros that desugar to ordinary Rust and compile on the stable toolchain — it adds no grammar — and the precise, vocal slice of the Rust audience will answer "it's not a language, it's a macro library" and win the exchange before it begins. "Extension" avoids that trap: an embedded, macro-based DSL is fairly called a language extension in ordinary usage, so the word keeps the ambition while staying on the right side of the honesty axis. -This is the defensible version of the ambition, and it keeps most of the upside while shedding the overclaim. Its selling point is that "extends Rust" still signals that CGP significantly enlarges what the language can express, matching the project's own observation that CGP now looks almost like a language extension, while "extension" implies addition-to rather than replacement-of, so it supports gradual adoption instead of fighting it. Its skepticism cost is milder than the bold version — some "do I have to learn a whole new thing" weight remains — and "extension" is a little vague on its own, so it needs a concrete completion: an extension that does *what*. On feasibility it is far more defensible, because an embedded, macro-based DSL is fairly called a language extension in ordinary usage, and pairing it with "on stable Rust, compiles to ordinary Rust" both stays honest and preempts the "it's just macros" dunk. The verdict is that this is the strongest *framing* of the ambition, provided it is completed with a concrete noun rather than left as a bare "extension." +**"Pluggable"** names the actual novelty, and the word was chosen against two alternatives that miss it. It is *not* "reusable," because to a Rust developer traits are already the reuse mechanism — one interface, many types — so "reusable trait implementations" describes something they believe they already have and points at the wrong axis. It is *not* "swappable," which says the right thing but is a less established software term and reads as a coinage next to a word of art. "Pluggable" names the real capability: one interface with many interchangeable implementations, each chosen per context — the thing Rust's coherence rules and the orphan rule normally forbid, and the reason CGP exists. -### Feature-first — "modular and reusable trait implementations for Rust" +**"Trait implementations"** grounds the abstraction in something concrete. It ties the broad "extension" framing to the trait system a working Rust developer uses every day, so the reader has a mental picture to attach the promise to rather than an abstraction to decode. It is the concrete hook the incumbent's "paradigm" lacked. -This line trades breadth for a concrete hook, and it is one of the safest strong candidates. Its selling point is that it ties CGP to a pain every Rust developer has felt: traits are rigid — one implementation per type, the orphan rule, no reuse of an impl across types — and "reusable trait implementations" is a promise the working developer can picture immediately, grounding the abstract idea of modularity in the trait system they use daily rather than leading with the bare word. Its skepticism cost is twofold: it still contains "modular," and it now risks *underselling* the breadth — abstract types, extensible data, the handler family — reproducing the incumbent's problem in a narrower form, which cuts against the project's sense that CGP has outgrown a trait-only story. On feasibility it is fully accurate. The verdict is that it is strong, concrete, and safe, and it is the best option whenever the hook can afford to be narrow and the breadth explained just below it. +**"At compile-time"** does two jobs. It defuses the runtime-dependency-injection misreading that "pluggable" might otherwise invite — the hidden-container, startup-configuration, reflection-cost fear — by stating up front that the choice is resolved statically. And it creates a productive tension with "pluggable" that *is* CGP's pitch in miniature: for a Rust reader "pluggable" normally implies runtime machinery — `dyn Trait`, a vtable, a plugin loaded at startup — so following it immediately with "at compile-time" says *plugin-style swapping, resolved statically, at zero runtime cost*. That tension is the part "reusable" could never set up, and it is why the qualifier is load-bearing rather than decorative. -### Benefit-first, dropping "modular" — "write an implementation once, choose it per context" +The line also makes one deliberate omission and carries one residual risk, both handled downstream. It does **not lead with the owned name**, "context-generic programming," because a name is not a pitch and the phrase is opaque on first contact — observed, not hypothetical, in CGP's own release discussions where readers said it "obscures more than it conveys" ([attention-and-engagement.md](attention-and-engagement.md)). The name is introduced *after* a concrete capability has landed, always paired with a descriptor, per [vocabulary.md](vocabulary.md). The residual risk is that "trait implementations" is narrower than "language extension" promises — it says nothing of the abstract types, extensible data, and handler family that make CGP broad — so the line slightly undersells its own breadth. That gap is closed not by cramming more into the tag line but by the pitch that follows it, below. -This family leads with a concrete outcome and sidesteps the loaded word entirely, which is its main virtue. Its selling point is that "write it once, swap it for tests versus production, chosen at compile time" names a pain the pragmatist recognizes and answers the runtime-DI fear in the same breath, without ever saying "modularity." Its skepticism cost is length and jargon: the phrasing is less punchy than a headline wants, and "context" is CGP's own term that a cold reader does not parse. On feasibility it is accurate. The verdict is that it is excellent as a *subline* — the honest mechanism beneath a shorter hook — but a little long and jargon-adjacent to carry the headline alone. +## What to say after the tag line — building the high-level pitch -### The category name — "context-generic programming" +The tag line earns the reader's next few seconds; the lines immediately after it are where attention is converted into understanding, and they should descend a deliberate funnel from reassurance to concrete pain to the owned name. The tag line is the top of that funnel, not the whole of it, so a piece that stops at the line leaves both the skeptic's first objection and the reader's "so what" unanswered. Build the pitch in the order below, taking as many rungs as the format's patience allows and stopping wherever it runs out. -The project's own name is a candidate in its own right, and its role is specific. Its selling point is that it owns a unique, searchable term, that "generic programming" carries real respect in the Rust and C++ worlds, and that "generic over the context" is the literal technical thesis — coining a category can win long-term mindshare the way "reactive programming" did. Its skepticism cost is that it is opaque on first contact: a name is not a pitch, as [selling-points.md](selling-points.md) warns, and a skimmer learns nothing from it while an unkind reader hears it as academic. This cost is observed, not hypothetical — in CGP's own release discussions multiple readers said the phrase obscures more than it conveys and that "coining a new phrase makes it harder to understand," reaching instead for "structural typing" or "duck typing for statically-typed code" to describe what they thought CGP was. That both confirms the opacity and surfaces a plainer bridge term worth testing in body copy (with the caveat that CGP is nominal-and-wired, not truly structural); see [attention-and-engagement.md](attention-and-engagement.md). On feasibility it is perfectly accurate, being the name. The verdict is to keep it as *the name*, always paired with a plain-language descriptor, and never to ship it alone as the tag line. +The first line after the tag line is the **reassurance subline**, and it does the skeptic's work before the skeptic can. It states the mechanism the hook left out — "still ordinary Rust, on a stable toolchain, with no runtime cost, adopted one trait at a time" — which heads off the two misreadings the line is most prone to: that "language extension" means a new language to learn (it does not — it is a library you add gradually) and that "pluggable" means a runtime framework (it does not — the resolution is static). This is the layered form the judging rules call for: the tag line hooks, the subline defuses. -### The borrowed analogy — "TypeScript for Rust" +The second rung is the **breadth line**, which repays the tag line's one debt. Because "trait implementations" undersells how far CGP reaches, a single sentence should name the rest of the surface — that beyond swappable trait implementations, CGP adds abstract types a context chooses for itself, extensible records and variants, and a family of composable handler and computation components. This turns "an extension for one thing" into "an extension broad enough to earn the word," without weighing down the line itself. -An analogy can do the work of a paragraph in three words, and this one is tempting, but it does not survive as a tag line. Its selling point is instant legibility: it borrows TypeScript's proven "a superset that compiles down and adds an expressive layer, and it's still the language you know" positioning, which conveys both power and gradual adoption at once. Its skepticism cost is that the analogy misleads under scrutiny — TypeScript adds static typing to a *dynamically* typed language, whereas Rust is already statically typed, so what CGP adds is not what TypeScript adds, and the precise audience will reject the comparison. On feasibility it is low as a headline, because honesty-is-strategy warns against a frame that breaks the moment it is examined, though it is serviceable as a one-time explanatory analogy in body copy with the caveat stated. The verdict is: do not headline it; use it once, carefully, only to convey the superset-that-compiles-to-Rust intuition, then set it aside. +The third rung, and the most persuasive, is a **concrete pain** — a problem the reader has already felt, stated before any mechanism. Lead with a felt frustration and let CGP be the answer to it: wanting a fake implementation for tests and the real one in production without a dependency-injection framework; needing to implement a trait for a type you do not own and hitting the orphan rule; watching a single trait grow into a monolith because there was no way to split its implementations. These before-and-after stories live in [problems-solved.md](problems-solved.md), and one of them, chosen for the channel's dominant reader, should almost always precede the abstract description of what CGP *is*. -### Audience-specific hooks — strong for a channel, wrong for the front page +The final rungs introduce the **owned name** and the **call to action**. Only after a concrete capability has landed should the piece name the paradigm — "this is what we call *context-generic programming*" — always beside a plain descriptor, so the name attaches to an understanding the reader now holds rather than standing in for one they do not. The call to action is then matched to the reader's stage in the funnel: a quickstart for the skimmer ready to try it, a link to an honest maturity-and-cost discussion for the evaluator weighing a bet, and never a request to adopt a codebase from a reader who has known CGP for thirty seconds. -A separate class of phrasings are excellent hooks for one reader and poor master tag lines, and it is worth naming them so they are not misused. Lines like "overlapping trait impls, made safe," "type classes without the orphan rule," and "compile-time dependency injection without the framework" each land hard with one profile — the coherence-aware type-system reader, the Haskeller, the enterprise developer — and each is developed as an audience-tuned pitch in [selling-points.md](selling-points.md). But every one of them is either too niche for a cold general audience or, in the DI case, imports the exact baggage a general tag line must avoid. The verdict is to keep these for audience-targeted channels per [reader-profiles.md](reader-profiles.md) and never to make one the project's front-page line. +One devrel note governs the whole sequence: concede the cost somewhere in it. CGP is more machinery than a plain trait, and for a capability with a single implementation a plain trait is the better tool; saying so plainly is what makes the rest of the pitch believable to an audience expert at detecting spin, and it costs nothing because it is true. -## What the analysis points to +## Candidate introductions -Several findings fall out of the comparison, and together they describe a strategy rather than a single winning phrase. +The drafts below show the tag line and the pitch working together as opening copy for a newcomer, each tuned to a different venue and dominant reader, with a short note on the moves it makes. They are templates for the moves, not approved words: adapting one to a real piece means re-running the decisions for that channel's reader — above all swapping in the concrete pain that reader feels most sharply — rather than find-and-replacing the copy. Every CGP claim in them is bound by the knowledge base's synchronization rule, so verify each against the source and the `/cgp` skill before it ships. -The first is to **retire "modular" as the lead word.** It is the weakest hook in the field and the only one that actively repels part of the audience, so modularity should survive as a supporting descriptor — and, when used, be immediately differentiated from its Spring connotations by pairing it with "compile-time," "zero-cost," and "explicit," the three properties that distinguish CGP from the runtime frameworks that gave "modular" its bad name. +**For a README or landing page, above the fold — skimmer and evaluator.** This intro leads with the line, reassures on the toolchain gamble immediately, and shows breadth before the reader scrolls. -The second is to **capture the "extends Rust" ambition, but as "extension," never "a language" or "a superset."** The softer word is both the honest one — CGP is macros producing stable Rust, not a new grammar — and the pro-adoption one, since "extension" adds to what the reader knows while "a language of its own" tells them to leave it behind. The bold word overclaims to the audience most able to catch it and simultaneously fights CGP's best reassurance, which is a rare double loss. +> CGP is a language extension for Rust, with pluggable trait implementations at compile-time. It is an ordinary library on stable Rust — no forked compiler, no new toolchain — that lets one trait have many interchangeable implementations and lets each type choose which one it uses. You write that choice as wiring in one place, and the compiler resolves it statically, so there is no runtime cost and nothing to configure at startup. The same machinery gives you abstract types each context picks for itself, extensible records and variants, and a family of composable handlers — and you can adopt any of it one trait at a time, leaving the rest of your code exactly as it is. -The third is to **lead with a concrete capability or the extension framing, complete it with a plain descriptor, and keep "context-generic programming" as the owned name beneath.** No single element does every job: the name owns the category but does not pitch, the capability pitches but does not convey breadth, the extension framing conveys breadth but needs a concrete completion. Layering them lets each do the job it is good at. +*Moves: tag line verbatim; reassurance subline ("ordinary library on stable Rust," "resolved statically," "no runtime cost"); breadth line naming abstract types, extensible data, and handlers; gradual-adoption close. Holds the concrete code for the screen just below.* -The fourth is to **layer the line into a hook and a subline**, because the subline is where the skimmer's and skeptic's first objection is defused. The hook earns the click; the subline says "still Rust, checked at compile time, zero runtime cost" and heads off the runtime-container and new-language misreadings before they form. +**For a launch post — the pragmatic skimmer, led by pain.** This intro opens on a frustration the reader has felt and makes the tag line the resolution, defusing the "another DI framework" reflex in the same breath. -Drawing these together, the recommended shortlist below is a starting point to validate with the community, not a final choice, since which line performs best is an empirical question that only real audiences answer: +> If you have ever wanted two implementations of the same trait — a fake for your tests and the real thing in production — you have met the wall Rust puts up: one implementation per type, and no implementing a trait you do not own for a type you do not own. CGP takes that wall down. It is a language extension for Rust that gives you pluggable trait implementations, chosen per type and resolved entirely at compile-time, with no dependency-injection framework and not a drop of runtime overhead. It is a library on the stable toolchain; you can add it to a single module and leave everything else untouched. -- **Hook:** "Context-generic programming" — **subline:** "A Rust language extension for reusable, swappable trait implementations — checked at compile time, with zero runtime cost." This leads with the owned name, carries the ambition honestly through "extension," grounds it in the concrete "reusable, swappable trait implementations," and defuses the two big misreadings in the subline. -- **Hook:** "Reusable, swappable trait implementations for Rust" — **subline:** "A language extension that lets one interface have many implementations, chosen per context and resolved at compile time." This leads with the concrete capability for the pragmatist and skimmer, and uses the subline to convey the breadth and the mechanism. -- **Hook:** "Rust traits, extended" — **subline:** "Write an implementation once, reuse it across types, and choose it per context — all at compile time, still ordinary Rust." This is the punchiest, trading some precision in the hook for momentum, with the subline carrying the honesty. +*Moves: opens on the mock-versus-real and orphan-rule pains from [problems-solved.md](problems-solved.md); tag line woven in as the answer, not the opener; "no dependency-injection framework" and "at compile-time" preempt the runtime-DI dismissal; gradual-adoption reassurance closes.* -The lines to avoid are equally clear: do not lead with "a modular programming paradigm" (inert, and repels on its hook word), do not claim "a language of its own" or "a superset of Rust" (overclaims to the pedantic audience and fights gradual adoption), and do not headline "TypeScript for Rust" (a frame that breaks under scrutiny). Whatever the project chooses, the next step is to float the shortlist in the channels where the [reader profiles](reader-profiles.md) actually gather and watch which hook earns attention without drawing the "it's just macros" or "another DI framework" reply — because the tag line's real grade is the reaction it provokes, not the case made for it here. +**For a talk or a spoken introduction — a general audience, curse-of-knowledge guarded.** This intro is plain enough to say aloud, defines the load-bearing word, and defers the name. + +> Here is the shortest way I can put it. CGP is a language extension for Rust, with pluggable trait implementations at compile-time. "Pluggable" is the word that matters: in ordinary Rust a trait has one implementation per type, but with CGP you can write several and plug in whichever one a given context should use — and because the plugging happens at compile-time, it costs nothing when the program runs. It is all still ordinary Rust on the stable toolchain. Under the hood we call this context-generic programming, but you do not need the name to use it. + +*Moves: tag line stated, then its key word unpacked with the one-impl-per-type contrast; "at compile-time" reframed as the zero-cost promise; the owned name introduced last and explicitly marked optional; every sentence plain enough to speak.* + +**For a comparison piece or a design write-up — the evaluator, cost conceded.** This intro names the paradigm early for a reader who wants the category, explains the core idea in one sentence, and draws CGP's boundary honestly. + +> CGP — context-generic programming — is a language extension for Rust that gives you pluggable trait implementations at compile-time. The idea underneath is small: separate the trait you *call* from the trait you *implement*, and let each context wire the two together, so one interface can have as many implementations as you like while every context picks its own. It all desugars to ordinary trait impls on stable Rust and is resolved statically, so the flexibility costs nothing at runtime. It is also more machinery than a plain trait, and for a capability that will only ever have one implementation a plain trait is still the right choice — CGP earns its keep when you genuinely need more than one, or need to choose per context. + +*Moves: name introduced up front because this reader wants the category; the consumer/provider split stated in one plain sentence; the compile-time, zero-cost reassurance; and an explicit concession of where a plainer tool wins, per [positioning.md](positioning.md), which is what earns this reader's trust.* + +## Keeping this document in sync + +Because the tag line is now settled rather than under selection, any change to it ripples across the section and must be propagated in the same change. The line and its layered pitch appear as guidance in [formats.md](formats.md) and as finished copy in [worked-examples.md](worked-examples.md); the wording rules that justify it — retire "modular" as a lead word, prefer "extension" over "a language"/"a superset," never ship the name alone — are enforced in [vocabulary.md](vocabulary.md) and [key-features.md](key-features.md); and the empirical caveat that publication is the real measurement lives in [attention-and-engagement.md](attention-and-engagement.md). When the line changes, revisit each of those. The catalog entry for this document in [README.md](README.md) must also match its current contents. diff --git a/docs/communication-strategy/vocabulary.md b/docs/communication-strategy/vocabulary.md index 77e24110..807f8c08 100644 --- a/docs/communication-strategy/vocabulary.md +++ b/docs/communication-strategy/vocabulary.md @@ -18,6 +18,7 @@ The established CGP vocabulary is the same in public writing as in the rest of t - **Provider** — a named, swappable implementation of a component. Introduce it as "one implementation you can choose," which is the word's whole job; avoid explaining that it is a zero-sized marker type until the reader is reading generated code. - **Wiring** — choosing which provider implements each component for a context. Introduce it as "a small table that says which implementation to use," and lean on the table image, since it is the single most load-bearing analogy in CGP writing. - **Impl-side dependency** — a requirement a provider states in its own implementation rather than in the interface callers see. Introduce it as "the provider declares what it needs, and callers never see it," because the encapsulation benefit is the point and the phrase "impl-side" means nothing cold. +- **Pluggable trait implementations** — the chosen lead descriptor for the core capability, from [tag-lines.md](tag-lines.md): one interface with many interchangeable implementations, each chosen per context. Prefer "pluggable" as the novelty word and pair it with "at compile-time," because the tension between the two — pluggability a Rust reader expects at runtime, delivered statically — is the pitch in miniature. - **Context-generic programming** — the name of the paradigm, never the pitch. Introduce it only after a concrete capability has landed, always paired with a plain descriptor, per [tag-lines.md](tag-lines.md). ## Terms to defer, and how to reveal them @@ -40,6 +41,7 @@ A handful of words reliably create the misunderstandings the section spends its - Avoid **"blazingly fast"** and any **unbenchmarked "faster than."** Say **"there is no runtime cost to compare,"** which is the honest and stronger claim. - Avoid **"no boilerplate."** Say CGP **"moves the wiring into one readable place,"** because there is wiring and the reader will find it. - Avoid **"replaces traits"** or **"a new language."** Say **"a superset of ordinary traits"** and **"a library on stable Rust," "an extension,"** never "a language of its own," per [tag-lines.md](tag-lines.md). +- Avoid **"reusable"** as the word for the novelty — to a Rust reader traits are already the reuse mechanism, so "reusable trait implementations" names nothing new and points at the wrong axis. Say **"pluggable"** (or "interchangeable," "many implementations chosen per context"), which names the capability coherence normally forbids, per [tag-lines.md](tag-lines.md). - Avoid **"just"** in a competitor's description — "just macros," "just another DI framework" are the reader's dismissals, not ours, and echoing them concedes the frame. - Avoid overstating maturity — **"works on stable Rust today"** is true and worth saying, while **"production-proven at scale"** needs evidence the [evaluator](reader-profiles.md) will notice is missing. diff --git a/docs/communication-strategy/worked-examples.md b/docs/communication-strategy/worked-examples.md index ed07d6f8..abdb4f1a 100644 --- a/docs/communication-strategy/worked-examples.md +++ b/docs/communication-strategy/worked-examples.md @@ -6,7 +6,7 @@ This document assembles the section's guidance into finished pieces of public wr These drafts exist because the rest of the section is prescriptive and abstract, and a marketing-naive expert learns faster from one finished model than from twelve documents of rules. Each is a model of one artifact from [formats.md](formats.md), written the way that format's playbook prescribes, then followed by a note that names each move and its source. Read the draft first as a reader would, front to back and fast; then read the annotations to see the machinery behind it. The point is not to copy the copy but to watch the whole apparatus operate at once, so you can run it yourself on your own piece. -Treat every draft as an illustration, not approved copy, because two honesty caveats travel with each one. The tag lines they use are the candidates from [tag-lines.md](tag-lines.md) that the project has still to validate with real audiences, so they are shown as plausible starting points rather than settled choices — [attention-and-engagement.md](attention-and-engagement.md) explains why publication itself is the real measurement. And every CGP claim and code snippet is bound by the knowledge base's synchronization rule exactly as a selling point is: verify it against the source and the `/cgp` skill before it ships, because a stale claim in a model draft is copied straight into real copy. +Treat every draft as an illustration, not approved copy, because two honesty caveats travel with each one. The tag line they use is the chosen one from [tag-lines.md](tag-lines.md); it is settled for consistency's sake, but publication remains the real measurement of any hook, so treat the copy as a strong starting point rather than a guarantee — [attention-and-engagement.md](attention-and-engagement.md) explains why. And every CGP claim and code snippet is bound by the knowledge base's synchronization rule exactly as a selling point is: verify it against the source and the `/cgp` skill before it ships, because a stale claim in a model draft is copied straight into real copy. The code uses the modern idioms the `/cgp` skill teaches, so a reader who borrows a draft learns the current idiom rather than a dialect to unlearn. A provider is written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md), a value is read with [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) or an [`#[implicit]`](../reference/attributes/implicit.md) argument, and wiring is a [`delegate_components!`](../reference/macros/delegate_components.md) table — the same forms [problems-solved.md](problems-solved.md) uses, and for the same reason. @@ -16,7 +16,7 @@ The launch post on Lobsters or the Rust subreddit is the format to get right fir ### The draft -> **Reusable, swappable trait implementations for Rust** +> **Pluggable trait implementations for Rust, at compile-time** > > Every Rust project eventually needs to swap a real implementation for a fake one — the real email sender in production, a recording stub in tests. The usual routes each cost something. A `Box` pays for dynamic dispatch and spreads a trait object through your types. A generic `` parameter has to be threaded through every layer that touches it, and it multiplies as more dependencies join. A dependency-injection crate brings machinery most of us would rather not. > @@ -58,7 +58,7 @@ delegate_components! { TestApp { EmailSenderComponent: RecordEmails } } Every choice above is a rule from elsewhere in the section, and naming them shows how to reproduce the result rather than the wording. The moves, in reading order: -- **The title is a concrete capability, not the paradigm name.** "Reusable, swappable trait implementations for Rust" is the shortlist hook from [tag-lines.md](tag-lines.md), chosen over "context-generic programming" because the title is the whole pitch for most of this audience, per the [launch-post playbook](formats.md). +- **The title is a concrete capability, not the paradigm name.** "Pluggable trait implementations for Rust, at compile-time" is the concrete-capability half of the chosen tag line in [tag-lines.md](tag-lines.md), used over "context-generic programming" because the title is the whole pitch for most of this audience, per the [launch-post playbook](formats.md). - **It opens on a pain, not the paradigm.** The mock-in-tests swap is the most universal problem CGP removes and the entry to lead with for the working developer, drawn straight from [problems-solved.md](problems-solved.md) and keyed to that [reader profile](reader-profiles.md). - **It shows the workarounds the reader already writes.** Naming `Box`, the threaded generic, and the DI crate lets the CGP version arrive as relief rather than as a new thing to learn — the before/after discipline of [problems-solved.md](problems-solved.md). - **It concedes the cost and the boundary in plain words.** "This is more than a single trait needs … CGP would be over-engineering" defuses the "why not just traits" and "over-engineered" reflexes from [skepticism.md](skepticism.md) by drawing the line [positioning.md](positioning.md) draws — and the concession is what makes the rest believable to this audience. @@ -77,9 +77,9 @@ The README's first screen is where an evaluator and a skimmer both decide in sec > **Context-Generic Programming** > -> *A Rust language extension for reusable, swappable trait implementations — checked at compile time, with zero runtime cost.* +> *A language extension for Rust, with pluggable trait implementations at compile-time.* > -> A library on stable Rust — no nightly, no fork. `cargo add cgp` +> Still ordinary Rust — no nightly, no fork, and zero runtime cost. `cargo add cgp` > > --- > @@ -135,7 +135,7 @@ fn main() { The README makes different promises to two readers at once, and each element serves one or both. The moves: -- **The tag line is layered.** The owned name, a plain descriptor, and a subline that heads off the runtime-container and new-language misreadings are the layered form from [tag-lines.md](tag-lines.md); the subline does the skeptic's work by saying "checked at compile time, zero runtime cost" before the reader supplies a worse reading. +- **The tag line is layered.** The owned name as the title, the chosen descriptor beneath it, and a reassurance line under that are the layered form from [tag-lines.md](tag-lines.md); the reassurance does the skeptic's work by saying "still ordinary Rust — no nightly, zero runtime cost" before the reader supplies a worse reading of "language extension" or "pluggable." - **It states "a library on stable Rust" and the install line early.** The [evaluator](reader-profiles.md) is scanning for the toolchain gamble, so the reassurance and `cargo add cgp` come before the prose, per [formats.md](formats.md) and the stable-Rust [selling point](selling-points.md). - **The feature set is the ruthless few, and each title avoids a repellent word.** Three of the canonical headline features from [key-features.md](key-features.md) appear, each title leading with a concrete capability or a recognized Rust term rather than "modular," "macros," or "magic," and each sentence carrying its honest qualifier ("at compile time," "made safe because … explicit and local"). - **Runnable code sits above the fold.** A reader wants to see the code before the prose, and CGP's own launch feedback asked for exactly a runnable example ([attention-and-engagement.md](attention-and-engagement.md)); the Hello World shows a component, a provider, a getter, and wiring in the smallest honest form. From ba561607dd97d66104101b877de6178e6c1fd0f4 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Mon, 20 Jul 2026 21:28:08 +0200 Subject: [PATCH 4/5] Remove unused imports --- crates/tests/cgp-tests/tests/dispatching/use_delegate_getter.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/tests/cgp-tests/tests/dispatching/use_delegate_getter.rs b/crates/tests/cgp-tests/tests/dispatching/use_delegate_getter.rs index 03cbdcb7..6c155649 100644 --- a/crates/tests/cgp-tests/tests/dispatching/use_delegate_getter.rs +++ b/crates/tests/cgp-tests/tests/dispatching/use_delegate_getter.rs @@ -456,7 +456,6 @@ snapshot_cgp_getter! { mod derive_delegate { use core::marker::PhantomData; - use cgp::prelude::*; use cgp_macro_test_util::snapshot_delegate_and_check_components; use super::*; @@ -592,7 +591,6 @@ mod derive_delegate { mod derive_delegate2 { use core::marker::PhantomData; - use cgp::prelude::*; use cgp_macro_test_util::snapshot_delegate_components; use super::*; From 1e2fdd9b0e1f6b74cc83fe5f062ba45575af4982 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Mon, 20 Jul 2026 21:36:46 +0200 Subject: [PATCH 5/5] Update compile-fail-tests snapshot --- .../acceptable/cgp_fn/use_type_nested_unsatisfied.stderr | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_nested_unsatisfied.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_nested_unsatisfied.stderr index fbf25663..0b95e975 100644 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_nested_unsatisfied.stderr +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_nested_unsatisfied.stderr @@ -82,7 +82,7 @@ note: required by a bound in `GetScalar` | ---------- required by a bound in this trait = note: this error originates in the attribute macro `cgp_type` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: NoScalar does not contain any DelegateComponent entry for ScalarTypeProviderComponent +error[E0277]: the trait bound `NoScalar: DelegateComponent` is not satisfied --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:49:5 | 49 | App: GetScalar, @@ -93,7 +93,6 @@ help: the trait `DelegateComponent` is not implemen | 37 | pub struct NoScalar; | ^^^^^^^^^^^^^^^^^^^ - = note: You might want to implement the provider trait for ScalarTypeProviderComponent on NoScalar = help: the following other types implement trait `DelegateComponent`: `BuildAndMergeOutputs` implements `DelegateComponent` `BuildAndMergeOutputs` implements `DelegateComponent` @@ -138,7 +137,6 @@ help: the trait `IsProviderFor` is not im | 37 | pub struct NoScalar; | ^^^^^^^^^^^^^^^^^^^ - = note: You need to add `#[cgp_provider(ScalarTypeProviderComponent)]` on the impl block for CGP provider traits = help: the following other types implement trait `IsProviderFor`: `BindErr` implements `IsProviderFor)>` `BindErr` implements `IsProviderFor)>`