diff --git a/README.md b/README.md index cf4d6d2bc..ece5f6934 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,46 @@ -# Context-Generic Programming (CGP) +# Context-Generic Programming (CGP) for Rust [](https://github.com/contextgeneric/cgp/blob/main/LICENSE) [](https://crates.io/crates/cgp) +[](https://docs.rs/cgp) [](https://github.com/contextgeneric/cgp/actions/workflows/tests.yml)   **A language extension for Rust, with pluggable trait implementations at compile-time.** -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. +Still ordinary Rust: a library on the stable toolchain, with provider selection resolved at compile +time, adopted one trait at a time. Beyond interchangeable implementations, CGP adds abstract types +chosen by each context, so error and runtime types need not be separate parameters through every +layer. It also supports extensible records and variants, and composable handlers. + +**[Website](https://contextgeneric.dev/) · [Quickstart](https://contextgeneric.dev/docs/quickstart) · [Tutorials](https://contextgeneric.dev/docs/tutorials/hello) · [Reference](https://contextgeneric.dev/docs/reference/) · [crates.io](https://crates.io/crates/cgp) · [API docs](https://docs.rs/cgp)** > [!IMPORTANT] -> This repository describes **v0.8.0**, which is **not compatible** with v0.7.0 — 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. +> This repository describes **v0.8.0**, which is **not compatible** with v0.7.0, because 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. -**[Website](https://contextgeneric.dev/) · [crates.io](https://crates.io/crates/cgp)** +## Install -## Key features +```bash +cargo add cgp +``` -CGP's headline promises are few and deliberately concrete. +Then bring everything into scope with a single import: -- **One interface, many implementations.** Write many interchangeable implementations of the same interface and choose between them per context — including the overlapping and orphan implementations Rust normally forbids, made safe because every choice is explicit and local. -- **Zero-cost abstraction.** Everything is resolved at compile time and compiles down to direct calls, so the flexibility costs nothing at runtime and unused implementations never reach the binary. -- **Type-safe wiring.** All wiring is checked at compile time, so a missing dependency is a build error rather than a runtime failure — in safe Rust, with no `dyn`, `Any`, or reflection. -- **Abstract over every dependency.** Write core logic that names its error type, runtime, and I/O abstractly and let each context supply the concrete choice, which keeps the core `no_std`-friendly — from embedded systems to WebAssembly. -- **Still ordinary Rust.** CGP is a superset of ordinary traits that you adopt one piece at a time: providers read like normal impls and implicit arguments like normal parameters, so a codebase can use it in one corner and stay otherwise vanilla. +```rust +use cgp::prelude::*; +``` + +CGP requires **Rust 1.89+** and runs on the **stable** toolchain, and needs neither a nightly toolchain nor a fork. The +[quickstart](https://contextgeneric.dev/docs/quickstart) takes it from there. ## A quick look -Real operations depend on infrastructure — a database, object storage — and the usual ways of supplying it do not scale. Passing each dependency as an argument threads long lists through every layer, while grouping them into a struct and adding methods locks the logic to that one concrete type, so it cannot be reused for a test, an embedded build, or a different backend. CGP lets you write the operation once against the capabilities a context provides. +Real operations depend on infrastructure such as a database or object storage, and the usual ways of +supplying it do not scale. Passing each dependency as an argument threads long lists through every +layer, while grouping them into a struct and adding methods locks the logic to that one concrete +type, so it cannot be reused for a test, an embedded build, or a different backend. CGP lets you +write the operation once against the dependencies a context provides. ```rust use cgp::prelude::*; @@ -42,7 +55,7 @@ pub struct App { // Write the operation once. `#[implicit]` reads `database` from the context; // `user_id` stays an ordinary argument. Any context carrying a matching -// `database` field gains `get_user` — no wiring, no trait impls to write. +// `database` field gains `get_user` without wiring or trait impls. #[cgp_fn] #[async_trait] pub async fn get_user( @@ -54,7 +67,9 @@ pub async fn get_user( } ``` -`get_user` reads like an ordinary async function, yet it works on any context with a matching field — checked at compile time, so a context missing `database` is a build error rather than a runtime failure. When a step needs *more than one* implementation, promote it to a component and let each context pick a provider: +`get_user` reads like an ordinary async function, yet it works on any context with a matching field, checked at compile time, so a context missing +`database` is a build error rather than a runtime failure. When a step needs *more than one* implementation, promote it to a component and let each +context pick a provider: ```rust // One interface... @@ -87,70 +102,130 @@ delegate_components! { } ``` -`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.) +`App` fetches from Amazon S3, and `GCloudApp`, a context carrying a Google Cloud storage client +instead, fetches from Google Cloud. Neither uses `dyn` or runtime dispatch, the choice is one +line that a search finds, and code that calls `self.fetch_storage_object(..)` never changes. (Method +bodies are elided here for brevity.) -## Installation +## Key features -Add the latest stable release with Cargo: +- **One interface, many implementations.** Write interchangeable implementations of one interface and choose between them per application. Separate provider types let implementations coexist while each application makes its choice explicit. +- **Zero-cost abstraction.** Provider selection resolves at compile time and compiles down to direct calls, without a runtime container or vtable lookup, and unused providers are never instantiated. The selected code still does its own work. +- **Type-safe wiring.** Check the wiring at compile time, with `check_components!` or by using the component, so a missing dependency fails the build rather than a call, in safe Rust, without `dyn`, `Any`, or reflection. +- **Abstract over every dependency.** Write core logic that names its error type, runtime, and I/O abstractly and let each context supply the concrete choice, which keeps the core `no_std`-friendly when the chosen implementations support it. +- **Still ordinary Rust.** Adopt CGP one component at a time: providers use familiar impl syntax, implicit arguments resemble parameters, and consumer traits can still be implemented directly. -```bash -cargo add cgp -``` +## Learn CGP -then bring everything into scope with a single import: +The documentation lives on the website, **[contextgeneric.dev](https://contextgeneric.dev/)**, and +each part answers a different question. -```rust -use cgp::prelude::*; -``` +| To… | Read | +| --- | --- | +| Get something running | [Quickstart](https://contextgeneric.dev/docs/quickstart) | +| Learn by building | [Tutorials](https://contextgeneric.dev/docs/tutorials/hello): a first program, then [area calculation](https://contextgeneric.dev/docs/tutorials/area-calculation) | +| Understand why CGP works this way | [Concepts](https://contextgeneric.dev/docs/concepts/): [consumer and provider traits](https://contextgeneric.dev/docs/concepts/consumer-and-provider-traits), [impl-side dependencies](https://contextgeneric.dev/docs/concepts/impl-side-dependencies), [coherence](https://contextgeneric.dev/docs/concepts/coherence), [namespaces](https://contextgeneric.dev/docs/concepts/namespaces) | +| Look up a construct | [Reference](https://contextgeneric.dev/docs/reference/): one page per macro, attribute, derive, provider, and trait | +| Compare it with what you know | [Comparisons](https://contextgeneric.dev/docs/comparisons/): dependency injection, type classes, ML modules, dynamic dispatch, and more | +| See it in a real program | [Projects](https://contextgeneric.dev/docs/projects/) | +| Choose how much CGP a problem needs | [The modularity hierarchy](https://contextgeneric.dev/docs/concepts/modularity-hierarchy) | -CGP requires **Rust 1.89+** and runs on the **stable** toolchain — no nightly, no fork. +## Readable errors: cargo-cgp -### Install cargo-cgp for readable errors - -CGP compiles to ordinary Rust, so a wiring mistake surfaces as a compiler error about generated types — often a wall of them with the real cause buried. **When you start with CGP, install [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp) alongside it.** cargo-cgp is CGP's error toolchain: a drop-in for `cargo check` that rewrites those errors into a compact, root-cause-first form, leading with the field or wiring that actually failed. It is the single biggest quality-of-life improvement for writing CGP, so treat it as part of your getting-started setup. +CGP compiles to ordinary Rust, so a wiring mistake surfaces as a compiler error about generated +types, often many errors with the real cause hard to find. **When you start with CGP, install +[`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp) alongside it.** It is CGP's error +toolchain: a drop-in for `cargo check` that rewrites those errors into a compact, root-cause-first +form, leading with the field or wiring that actually failed. `cargo cgp check` leads with the root +cause for the classes it recognizes, and the tool does not yet reshape every class. ```bash cargo install --locked cargo-cgp # the front-end (needs Rust 1.96 or newer) cargo cgp setup # provisions the compiler + driver it needs ``` -Then run `cargo cgp check` wherever you would run `cargo check`; it forces the pinned nightly that `cargo cgp setup` installed only for its own check, so your project keeps its toolchain untouched. (Prefer Nix? `nix run github:contextgeneric/cargo-cgp/v0.1.0 -- check` runs it without installing.) See its [README](https://github.com/contextgeneric/cargo-cgp) for the full guide. +Then run `cargo cgp check` wherever you would run `cargo check`; it forces the pinned nightly that +`cargo cgp setup` installed only for its own check, so your project keeps its toolchain untouched. +(Prefer Nix? `nix run github:contextgeneric/cargo-cgp/v0.1.0 -- check` runs it without installing.) +The [installation guide](https://contextgeneric.dev/docs/cargo-cgp/installation) covers every path, +[reading the output](https://contextgeneric.dev/docs/cargo-cgp/reading-output) explains the rewritten +errors, and the [error codes](https://contextgeneric.dev/docs/cargo-cgp/error-codes) catalog every +`[CGP-Exxx]` code. -cargo-cgp is optional and adds only the reading commands `check` and `expand`: keep building, running, and testing with ordinary cargo, since CGP is a plain library needing only stable Rust (1.89+), and reach for `cargo cgp check` when you want a wiring error made readable. Plain `cargo check` still works too, and is fine when you are not expecting a compile error. +cargo-cgp is optional and adds only the reading commands `check` and `expand`: keep building, +running, and testing with ordinary cargo, since CGP is a plain library needing only stable Rust +(1.89+), and reach for `cargo cgp check` when you want a wiring error made readable. Plain +`cargo check` still works too, and is fine when you are not expecting a compile error. -## When to use CGP — and when not +## When to use CGP, and when not -CGP is a tool with a boundary, and it earns its cost only past that boundary. The rule of thumb is to **reach for CGP when a capability needs more than one implementation and the choice belongs to the context — not before.** Concretely: +CGP is a tool with a boundary, and it earns its cost only past that boundary. The rule of thumb is to +**reach for CGP when an interface needs more than one implementation and the choice belongs to the +context, and not before.** Concretely: - **One implementation?** Use a plain trait or function. CGP would be over-engineering. - **A small, closed set of variants?** Use an `enum` and a `match`. -- **Implementations chosen at runtime** — plugins, config-driven selection, heterogeneous collections? Use `dyn Trait`; CGP resolves everything at compile time and gives up that runtime openness by design. -- **One implementation must be globally unique** — a single `Ord` for a map key, say? A coherent trait is the right tool; CGP's per-context choice is deliberately not global. +- **Implementations chosen at runtime**, such as plugins, config-driven selection, or heterogeneous collections? Use `dyn Trait`; CGP resolves everything at compile time and gives up that runtime openness by design. +- **One implementation must be globally unique**, as with a single `Ord` for a map key? A coherent trait is the right tool; CGP's per-context choice is deliberately not global. -CGP is a *superset* of ordinary traits, so adopting it is a climb from the plainer tool rather than a rejection of it. You can use it exactly where it pays and leave the rest of a codebase unchanged. +CGP is a *superset* of ordinary traits, so adopting it starts from the plainer tool rather than +replacing it. You can use it exactly where it pays and leave the rest of a codebase unchanged. The +[modularity hierarchy](https://contextgeneric.dev/docs/concepts/modularity-hierarchy) works through +the choice in more detail. -## Documentation +The costs are real and worth stating beside the benefits: more declarations and wiring, extra +compile-time work, verbose raw diagnostics, and concepts to learn. A delegation entry alone does not +verify its provider's dependencies, so add a check or use the component to force verification. + +## Project status -Learn CGP at the project website, **[contextgeneric.dev](https://contextgeneric.dev/)**, which hosts the guides, worked examples, and project blog. +CGP works today on stable Rust and already offers a broad feature set: components and providers, +abstract types, extensible records and variants, a handler and computation family, namespaces, and +modular error handling. It is also young, with the paradigm and its ecosystem still evolving, so +adopting it is a real decision rather than a foregone one. -> [!NOTE] -> The crates' API docs on `docs.rs` are still sparse. The website is the place to start while the inline documentation is filled in. +Two properties make it low-risk to try. Because CGP is a superset of ordinary traits, it can be +introduced in one corner of a codebase and stepped back from without a rewrite; and because it +adds nothing at runtime, it does not lock a project into a framework's lifecycle. If you are evaluating it +for a team, start small, and use the worked examples on the +[website](https://contextgeneric.dev/docs/projects/) to gauge the learning curve honestly. + +## Documentation -Contributors and coding agents have a second, deeper source: the [CGP knowledge base](https://github.com/contextgeneric/cgp-knowledge-base), which holds the exhaustive per-construct reference, the concept overviews, the writing guides, the compile-error catalog, the worked examples, and the internals of the macros in this repository. It is written for AI agents rather than for learners, so start at the website if you are learning CGP and reach for the knowledge base when you need the exact semantics of a construct or are changing this repository. Start from [AGENTS.md](AGENTS.md) for the conventions this repository follows, and [sibling-projects.md](sibling-projects.md) for the related repositories. +The [website](https://contextgeneric.dev/) is the main documentation: the quickstart, the tutorials, +the concepts, the construct reference, the comparisons, and the project pages. The crates' API +documentation on [docs.rs](https://docs.rs/cgp) covers the items themselves. -## Project status +> [!NOTE] +> The crates' API docs on `docs.rs` are still sparse. The website is the place to start while the +> inline documentation is filled in. -CGP works today on stable Rust and already offers a broad feature set: components and providers, abstract types, extensible records and variants, a handler and computation family, namespaces, and modular error handling. It is also young — the paradigm and its ecosystem are still evolving — so adopting it is a real decision rather than a foregone one. +Contributors and coding agents have a second, deeper source: the +[CGP knowledge base](https://github.com/contextgeneric/cgp-knowledge-base), which holds the +exhaustive per-construct reference, the concept overviews, the writing guides, the compile-error +catalog, the worked examples, and the internals of the macros in this repository. It is written for +AI agents rather than for learners, so start at the website if you are learning CGP and reach for the +knowledge base when you need the exact semantics of a construct or are changing this repository. The +[`/cgp` agent skill](https://contextgeneric.dev/docs/ai/skills/) teaches an assistant to read and +write CGP; it does not remove the reviewer's need to understand the code. -Two properties make it low-risk to try. Because CGP is a superset of ordinary traits, it can be introduced in one corner of a codebase and stepped back from without a rewrite; and because it imposes no runtime, it does not lock a project into a framework's lifecycle. If you are evaluating it for a team, start small, and lean on the worked examples on the [website](https://contextgeneric.dev/) to gauge the learning curve honestly. +Start from [AGENTS.md](AGENTS.md) for the conventions this repository follows, and +[sibling-projects.md](sibling-projects.md) for the related repositories. ## How the project is organized -CGP is a collection of small, layered crates, arranged so that low-level primitives stay independent of the high-level facade. Most users depend only on the single **`cgp`** crate, which re-exports the core and extra functionality through `cgp::prelude`. Pluggable error backends are opt-in as separate crates — `cgp-error-anyhow`, `cgp-error-eyre`, and `cgp-error-std`. Browse [`crates/`](crates) for the full layout. +CGP is a collection of small, layered crates, arranged so that low-level primitives stay independent +of the high-level facade. Most users depend only on the single **`cgp`** crate, which re-exports the +core and extra functionality through `cgp::prelude`. Pluggable error backends are opt-in as separate +crates: `cgp-error-anyhow`, `cgp-error-eyre`, and `cgp-error-std`. Browse [`crates/`](crates) for +the full layout. ## Contributing -Contributions, questions, and bug reports are welcome. Open an [issue](https://github.com/contextgeneric/cgp/issues) or a pull request, and see the [website](https://contextgeneric.dev/) to get oriented first. Unless stated otherwise, contributions are made under the terms of the Apache-2.0 license below. +Contributions, questions, and bug reports are welcome. Open an +[issue](https://github.com/contextgeneric/cgp/issues) or a pull request, and read the +[contribute page](https://contextgeneric.dev/docs/contribute) to get oriented first. Unless stated +otherwise, contributions are made under the terms of the Apache-2.0 license below. ### Development diff --git a/crates/macros/cgp-macro-test-util/README.md b/crates/macros/cgp-macro-test-util/README.md index 1c436069d..1d5a91d5b 100644 --- a/crates/macros/cgp-macro-test-util/README.md +++ b/crates/macros/cgp-macro-test-util/README.md @@ -1,12 +1,14 @@ -# `cgp-macro-test-util` +# cgp-macro-test-util: snapshot-testing macros for the CGP macros -Snapshot-testing macros for the CGP procedural macros. +**The macros that pin the code the CGP procedural macros generate, as inline `insta` snapshots.** + +**[Website](https://contextgeneric.dev/) · [Reference](https://contextgeneric.dev/docs/reference/) · [Implementation record](https://github.com/contextgeneric/cgp-knowledge-base/blob/main/cgp/implementation/entrypoints/snapshot_macros.md)** This crate exposes a family of `snapshot_*!` procedural macros that pin the code the core CGP macros generate. Each one does **two** things at once: it emits the real generated code into the surrounding module, exactly as the underlying CGP macro -would — so the traits, structs, and impls stay live and the rest of the module can -still wire them up and assert their runtime behavior — and it generates a `#[test]` +would, so the traits, structs, and impls stay live and the rest of the module can +still wire them up and assert their runtime behavior, and it generates a `#[test]` that asserts a pretty-printed string of that same code against an inline [`insta`](https://insta.rs) snapshot. Adding or removing a snapshot therefore changes only the golden assertion, never the compile-time or runtime coverage. @@ -15,7 +17,7 @@ Two documents carry what this README does not. The [implementation document](https://github.com/contextgeneric/cgp-knowledge-base/blob/main/cgp/implementation/entrypoints/snapshot_macros.md) in the knowledge base explains how the macros are built and what an invocation expands to; [crates/tests/AGENTS.md](../../tests/AGENTS.md) sets the convention for -*when* to snapshot — a macro's expansion is pinned only in the concept target that +*when* to snapshot: a macro's expansion is pinned only in the concept target that owns that macro's feature, and written plainly everywhere else. ## Crate layout @@ -63,9 +65,9 @@ so a snapshot is guaranteed to show what the production macros generate. | `snapshot_cgp_producer!` | `#[cgp_producer]` | | `snapshot_cgp_auto_dispatch!` | `#[cgp_auto_dispatch]` | -Each accepts the same argument forms as the macro it wraps — `snapshot_cgp_component!` -takes both `#[cgp_component(Greeter)]` and the brace form, for instance — precisely -because it drives the production entry function. +Each accepts the same argument forms as the macro it wraps, because it drives the +production entry function; `snapshot_cgp_component!` takes both +`#[cgp_component(Greeter)]` and the brace form, for instance. ## Anatomy of an invocation @@ -90,9 +92,9 @@ snapshot_cgp_getter! { } ``` -That emits the real `#[cgp_getter]` expansion — the consumer trait and its blanket -impl, the provider trait, the marker struct, and the `UseField`/`UseFields`/ -`RedirectLookup` provider impls — and then a `fn expand_has_name()` holding the same +That emits the real `#[cgp_getter]` expansion, which is the consumer trait and its +blanket impl, the provider trait, the marker struct, and the `UseField`/`UseFields`/ +`RedirectLookup` provider impls, and then a `fn expand_has_name()` holding the same code as a string. The test-function name must be unique within its module, so give each snapshot in a file a distinct one. diff --git a/crates/main/cgp/README.md b/crates/main/cgp/README.md index 8236647a7..2a2fe2eb7 100644 --- a/crates/main/cgp/README.md +++ b/crates/main/cgp/README.md @@ -1,13 +1,41 @@ -# `cgp` — Context-Generic Programming in Rust +# `cgp`: Context-Generic Programming in Rust -## Overview +**A language extension for Rust, with pluggable trait implementations at compile-time.** -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. The `cgp` crate is the facade over a collection of micro crates that provide it, and it re-exports everything through `cgp::prelude`. +The `cgp` crate is the facade over the collection of crates that provide CGP, and it re-exports +everything through `cgp::prelude`. 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 provider selection adds nothing at +runtime. -To learn more, see the website [contextgeneric.dev](https://contextgeneric.dev/) and the book [Context-Generic Programming Patterns](https://patterns.contextgeneric.dev/). +## Learn CGP -