# CLAUDE.md -- Rust Project Conventions
## Philosophy
This project follows **functional**, **type-driven**, and **domain-driven** design principles. The type system is the primary tool for correctness. If a state is illegal, it should be unrepresentable.
## Architecture
- **Modules by domain context**, not by technical layer. No `controllers/`, `services/`, `models/` splits.
- Each bounded context gets its own module with its own types. Cross-context communication happens through well-defined public interfaces.
- Prefer thin `main.rs`/`lib.rs` that wires contexts together.
## Types
- **Newtypes** for all domain primitives. Never pass raw `String`, `u64`, `f64`, etc. across function boundaries when they carry domain meaning.
- **Sum types (enums)** to model domain variants and state machines. Prefer enums over booleans or stringly-typed fields.
- **Phantom types** where compile-time state tracking is warranted.
- **No public struct fields**. All fields must be private. Expose access through getter methods and construct through associated functions or builder patterns.
- `#[must_use]` on pure functions and types whose values should not be silently dropped.
## Error Handling
- A single project-wide `Error` enum defined in a dedicated `error` module.
- Each variant wraps an underlying error type from a dependency or domain context.
- Implement `From<UnderlyingError> for Error` for every variant to enable `?` ergonomics throughout the codebase.
- Implement `std::fmt::Display` and `std::error::Error` by hand -- no `thiserror`, no `anyhow`.
- Domain logic returns `Result<T, Error>`. Never panic in library code.
## Style
- **Prefer `match` over `if`/`else`**. The only exception is when branching on a `bool` value, where `if`/`else` is preferred. For everything else, use `match`.
- **No `return` keyword**. Every function body is a single expression. Use `?` for early error propagation and combinators for control flow; never write an explicit `return`.
- **No `mut`**. All bindings and parameters must be immutable. Restructure with shadowing, combinators, or new bindings instead of mutation.
- **Combinators** (`map`, `and_then`, `filter`, `fold`, etc.) over imperative loops and match-and-early-return, where they remain readable.
- **Never match on `Option<_>`**. Use combinators instead: `map`, `and_then`, `unwrap_or`, `unwrap_or_else`, `filter`, `zip`, etc.
- **Prefer combinators on `Result<_, _>`**. Use `map`, `map_err`, `unwrap_or`, `unwrap_or_else`, `and_then`, etc. instead of pattern matching on `Ok`/`Err`. Reserve `match` on `Result` only when the arm logic is too complex for a single combinator chain.
- **Prefer `and_then` over nested pattern-matching**. When you would nest `match` arms to unwrap successive `Option`/`Result` layers, flatten with `and_then` (or `?`) instead.
- **Pure functions** -- isolate side effects at the boundaries. Core domain logic takes values and returns values.
- **No `unwrap()`/`expect()`**. Prohibited everywhere -- including tests, doctests, and `main`. Always propagate or handle errors explicitly.
- **No `loop` or `for`**. Use iterator combinators (`map`, `filter`, `fold`, `for_each`, etc.) or recursion. If you think you need a loop, restructure with combinators or a recursive function.
- **No `scan`**. Use `successors`, `fold`, or recursion instead.
- **No reference counting** (`Rc`, `Arc`). Prefer ownership, borrowing, and lifetime parameters. If a design seems to require shared ownership, restructure with indices, arenas, or explicit ownership trees.
**Exception**: comp-cat-rs's `Stream` API requires `Arc<dyn Fn(...)>` for `unfold`, `map`, and `fold`. Use `Arc` exclusively at these call sites.
- **No naked `as` casts**. Never use `as` for type conversions. Use `From`/`Into` for infallible conversions and `TryFrom`/`TryInto` for fallible ones.
- **Exhaustive matches**. Never use the `_` wildcard arm on enum matches. List every variant explicitly so adding a variant forces a compile error.
- Prefer **iterators** over indexed access.
## Traits
- Trait definitions follow domain boundaries -- a trait represents a domain capability, not a technical pattern.
- **No dynamic polymorphism**. Never use `dyn Trait` or trait objects. All dispatch must be static via generics or `impl Trait`. If a design seems to require `dyn`, restructure with enums or generics instead.
**Exception**: comp-cat-rs's effect types use `dyn` internally. We use its API as-is but introduce no additional `dyn` in our code.
- **Implement the standard trait, not an ad-hoc method**. If a method's signature and semantics match a trait from `core`/`std` (e.g., `Add`, `Sub`, `Mul`, `From`, `Into`, `Display`, `Iterator`, etc.), implement the trait instead of defining a standalone method.
## Linting
```toml
[lints.clippy]
all = { level = "deny", priority = -1 }
pedantic = { level = "warn", priority = -1 }
needless_pass_by_value = "warn"
manual_map = "warn"
```
## Verification
- **Always run `RUSTFLAGS="-D warnings" cargo clippy --all-targets`** before considering code complete. All warnings are errors -- code must pass cleanly.
- **Always run `cargo fmt`** after any code change before reporting work as done.
## Testing
- **Property-based tests** via `proptest` where types suggest invariants.
- Unit tests live in the same file as the code they test (`#[cfg(test)]` modules).
- Integration tests go in `tests/` organized by domain context, not by module path.
- Test names describe the property or behavior, not the function name.
- **Tests return `Result<(), Error>` and propagate failures with `?`**. No `assert!`, no `assert_eq!`, no `panic!`, no `unreachable!`, no `unwrap`, no `expect`.
## Dependencies
- **`comp-cat-rs`** is the required foundation crate. Always depend on the latest version on crates.io. Use its `Io`, `Resource`, `Stream`, `Fiber` types for all effectful code, its `Functor`/`Monad` traits as the core abstractions, and its `Kind` trait for higher-kinded type encoding. Do not roll your own effect types when `comp-cat-rs` provides them.
- **Delay `run` as long as possible**. Once inside a `comp-cat-rs` effect (`Io<_, _>`, `Stream<_, _>`, etc.), stay inside it by composing with combinators (`map`, `flat_map`, etc.). Call the `run` catamorphism only once at the outermost boundary (e.g., `main`, a handler entry point, a top-level test). Never escape an effect with `run` mid-pipeline only to re-enter it.
- Minimize other dependencies. Evaluate whether a crate is truly needed or if the functionality can be expressed with `std`, `comp-cat-rs`, and the type system.
- No `thiserror`, no `anyhow` -- error handling is explicit and hand-rolled (see above).
- **No path dependencies** for any crate that is published or intended to be published to crates.io. All deps in `Cargo.toml` use plain crates.io version specifications.
## Documentation
- **Always maintain a proper `README.md`**.
- **Doc comments on all public items**. Every public type, function, trait, method, and module must have `///` doc comments. Use `//!` at the top of `lib.rs` and major modules for module-level documentation.
- **Include `# Examples` sections** with runnable code blocks in doc comments for key public APIs. These double as doctests.
- **Doctests must not use `unwrap`, `expect`, or `unreachable!`**. Use the `# fn main() -> Result<(), Error> { ... }` hidden-main pattern with `?` propagation.
- **Use `[`backtick links`]`** to cross-reference other types, traits, and functions within doc comments so `rustdoc` generates hyperlinks.
- **Preview locally** with `cargo doc --no-deps --document-private-items --open`.
- **Run doctests** as part of the test suite: `cargo test --doc`.
### Generating and Publishing Docs
Every Rust repo must include `.github/workflows/docs.yml` to auto-publish documentation to GitHub Pages on push to `main`. The workflow runs `cargo doc --no-deps`, then deploys the `target/doc/` output.
To enable GitHub Pages for the repo: Settings > Pages > Source > GitHub Actions.
## Spike context
This crate is **spike 2** of a comp-cat-rs reformulation of a web engine targeting Tauri integration. Spike 1 (`lambda-cat`) established that comp-cat-rs idioms express a pure tree-walking interpreter cleanly. Spike 2 raises the bar: can the same idioms (no `mut`, no `Rc`/`Arc`, no interior mutability, no `unsafe`, no panics, exhaustive matches, static dispatch) express a heap of mutable reference cells and a mark-sweep garbage collector? Mutation is the layer where most "functional purists" reach for `RefCell`; the rule here is to express the heap as a persistent map whose every operation returns a new heap, with the collector pure-functional over those snapshots. If this spike fits cleanly, the path to `boa-cat` (with a real JS heap and a real GC) is open.