Conventions
This page collects the rules every contribution to Kathryn2 is expected to
follow. The source of truth is CLAUDE.md at the repository root (sections 3,
4, and 6); this page restates it for the web.
Naming conventions
Section titled “Naming conventions”Files and modules
Section titled “Files and modules”snake_case.rsfor files, matching the primary type they define (module.rsdefinesModule).- One primary type per file; small companion types (enums, helper structs) may live alongside.
- Submodule directories use a
mod.rsthat only re-exports — no logic.
PascalCasefor structs, enums, traits.- Ident types end with
Ident(HcpIdent,ModuleIdent). - Trait-object-suitable traits end with
-able(HcpAssignable,HcpReadable,HcpAccessible). - Enum variants:
PascalCase. Avoid C-styleSCREAMING_PREFIX_VARIANT.
Ident variables — the _i suffix
Section titled “Ident variables — the _i suffix”Any variable or field whose type is an *Ident handle (NcpIdent,
HcpIdent, FlowBlockIdent, ModuleIdent, …) must carry an _i suffix so
it is immediately clear it is a lightweight handle, not the object itself:
let state_i = arena.make_state_node(...); // NcpIdentlet asm_node_i = ...; // NcpIdentself.sync_reg_i: HcpIdent; // struct fieldSome older files still use bare names (state, asm, syn, …). Fix them as
you touch those files; do not do a mass rename in a single PR.
No single-letter handle names. Even for a short-lived local inside a
take/replace_back block, do not name an ident h / r / s. Give it the
same descriptive _i name it would have at the use site (e.g. user_hold_i,
user_reset_i). Single letters are only acceptable for the taken object
itself (e.g. let arb = arena.take_arb(...)), never for the handles read off
it.
Functions
Section titled “Functions”| Pattern | Meaning |
|---|---|
new(is_user_com, name, ...) | Full constructor |
mk(name, ...) | User-declared shorthand (is_user_com = true) |
make_<thing> | Arena factory, system/internal (is_user_com = false) |
mk_<thing> | Arena factory, user-declared (is_user_com = true) |
get_<field> / get_<field>_mut | Getters — no bare-field getters |
add_<thing> | Push into a Vec or arena |
get_<things> | Plural collection getter returning &Vec<…> |
take_<thing> / replace_back_<thing> | The only arena read/write surface (see Factories and CRUD) |
All functions are snake_case. The make_ / mk_ split matters: the Python
bindings always wrap the user path, which is why every host make_x
surfaces as mk_x in Python (see the
Python Layer).
flowchart LR
F["arena factory"] --> MAKE["make_thing<br/>is_user_com = false<br/>(system / internal)"]
F --> MK["mk_thing<br/>is_user_com = true<br/>(user-declared)"]
MK --> PY["Python binding mk_x<br/>(wraps the user path)"]
Constants
Section titled “Constants”SCREAMING_SNAKE_CASE.- Update-event priorities live as
DEFAULT_UE_PRI_*consts at module top (exposed to Python via the single-source-of-truth constant table).
Identifier prefixes
Section titled “Identifier prefixes”HwComponentType::global_prefix defines the canonical short prefix for each
HW type (REG, WIRE, SR_ST, MODULE, …). Use those when constructing
unique names in build_unique_name — do not invent new ones.
Code style
Section titled “Code style”All generated and edited code follows the project owner’s formatting style. Key rules:
- Column-align
:in struct fields and function parameters so types form a vertical column. - Collapse trivial getters to a single line; align return types across the group.
---- Section ----separator comments to divide logical groups insideimplblocks or files.- Align match/switch arms so
=>or:lines up vertically. - Multi-line signatures when there are 3+ parameters: one parameter per line, closing delimiter on its own line, trailing comma.
_isuffix on any variable or field whose type is a*Identhandle.- Brief comments — one sentence, explain why not what; no multi-paragraph docstrings.
A real excerpt showing several of these at once
(src/debug/config.rs):
pub struct DebugSink { pub(super) mode : OutputMode, pub(super) file_writer : Option<FileWriter>,}
impl DebugBuilder {
// ---- flag selection ----
/// Enable a single debug category. pub fn flag(mut self, f: DebugFlag) -> Self { self.flags.push(f); self }
/// Enable a slice of debug categories at once. pub fn flags(mut self, fs: &[DebugFlag]) -> Self { self.flags.extend_from_slice(fs); self }Architectural ground rules
Section titled “Architectural ground rules”These are covered in depth elsewhere in the Devbook, but they are also conventions in the sense that violating them will fail review:
- One owner per object: the arena. Never store
Box<dyn Trait>or owningRc/Arcof model objects; insert into the arena and pass*Identhandles (ModelArena). - No typed
get_<thing>/get_<thing>_mutonModelArena. Read and mutate throughtake_*/replace_back_*(Factories and CRUD). - One match per polymorphic family. New HCP/UE/flow-block variants add one arm to the single dispatch match, plus a trait impl — nothing else (Dispatch).
- Anything added to the arena goes in both
ModelArena::newandreset(Memory Model). - PyO3 stays behind the
pythonfeature. No PyO3 macro outsidesrc/applications/py/(Python Layer).
Contributor workflow expectations
Section titled “Contributor workflow expectations”- The baseline is 0 errors.
cargo buildmust complete cleanly (warnings are acceptable), andcargo build --features pythonmust also stay at 0 errors. Any new error you introduce is yours to fix. - Run
cargo buildafter non-trivial changes — do not batch up a day of edits and hope. - Do not mutate remote state without explicit approval. No
git push,cargo publish, or anything similar without the owner asking for it. - Default to editing existing files. Do not add documentation files to
the repository unless asked;
CLAUDE.mdis the exception. - New patterns go into
CLAUDE.md. When you introduce a pattern that future contributors must follow, document it there — project-scoped facts belong in the repo, not in any agent’s session memory.
Known gaps / roadmap
Section titled “Known gaps / roadmap”The Rust port is not feature-complete relative to the original Kathryn. Do not assume the following exist on the Rust side; if you need them, check first:
| Missing piece | Notes |
|---|---|
| Full controller-driven flow-block construction | buildAll / buildFlow orchestration; the Python DSL currently drives lifecycle explicitly |
Box, nest, PmVal, ModelInterface | Not ported |
ModuleSimEngine, ModuleGen, ModelDebugger | No simulation/codegen engine yet |
controller hooks | ctrl->on_module_init_components etc. — only clock_mode is ported |
When porting a struct that depends on one of these, scope the new struct to fields whose dependencies do exist rather than stubbing the missing ones.
A practical consequence for the Python layer: because controller hooks are
unported, the entire Python construction phase runs the active module at a
single ModuleInitStage::FlowBlockInit stage — the separate component/flow
two-phase split exists in the model but is unused for now.