rust-coding
Mission
- Own safe, idiomatic Rust patterns for the engine codebase.
When To Load
- Write Rust code.
- Review Rust code.
- Refactor Rust modules.
When To Skip
- Lua scripts.
- CAG files.
- Docs-only work.
Domain Knowledge
- mod.rs in src/ must contain only
pub mod, pub use, doc comments, and #[allow] attributes. Definitions belong in sibling files.
- No
#[cfg(test)] blocks in src/. Unit tests for private code go in tests/rust/unit/<module>_tests.rs, named <module>_tests.rs not <module>_test.rs.
- When external tests need a private seam, prefer
pub(crate) over pub and keep the exposed item narrow. Treat it as a testability boundary, not a public API expansion.
- If a
pub(crate) seam exists only for tests, keep its doc comment explicit about that intent so future refactors do not promote it accidentally.
src/lua_api/*_api.rs must stay thin: LuaUserData impls, add_methods, registration, and type conversions only. Business logic belongs in src/<module>/.
- Never hold
borrow_mut() or RefCell::borrow_mut() across a Lua callback invocation. The pattern is: extract all needed values while holding the borrow, release it, then invoke Lua.
- SharedState access rule:
{ let guard = state.borrow(); let val = guard.field.clone(); } /* borrow released */ call_lua(val). The guard must be dropped before any call that might re-enter Rust.
- Pinned library versions: mlua 0.9, wgpu 22, winit 0.30, rapier2d 0.32, rodio 0.17, fontdue 0.9. Do not bump without explicit authorization; each bump needs wgpu/winit API adjustments.
- When a Rust change touches public types or functions visible through
lurek.*, run tools/python.cmd tools/validate/validate_lua_api.py to catch shape drift in generated docs.
- Public changes must update
docs/specs/<module>.md in the same commit. Compiler green does not mean the task is done.
- Use
? for propagation but never let it cross a callback boundary silently. Closures passed to mlua must return mlua::Result; inner ? should map errors before they reach Lua with a clear message.
- Keep
unsafe blocks small, one-purpose, and accompanied by a // SAFETY: comment that explains the invariant. No unsafe for convenience when a safe alternative exists.
- Prefer explicit module imports over glob imports. Glob imports make it impossible to grep what the file actually depends on during refactors.
- Tests in
tests/rust/unit/ name their test functions test_<behavior>_<condition>. Keep each test assertion to one failure mode so failing tests name the problem immediately.
Rustdoc Comment Standards — AI-First, Compact
Comments exist for AI agents reading the code. Goal: maximum signal per token. No prose, no repetition, no filler.
Regular Rust files (src/ excluding src/lua_api/)
What requires a doc comment
Every one of the following lines must have a /// comment directly above it — no exceptions, no qualifications:
pub fn, pub async fn
fn (every private function, without exception)
pub struct, struct
pub enum, enum
pub mod (every occurrence, in any file)
pub type, pub const, pub static
impl Trait for Type — one /// line above: what the trait provides for this type
- Every field inside a struct — pub and private — one
/// line each
- Every variant inside an enum — one
/// line each
Plain impl Type { } (inherent impl): keep existing /// comments and improve them. Add /// when the block groups distinct functionality. Never remove existing /// on impl blocks.
File level (//!)
Use bullet-point format: each line starts with //! - . Proportional to file size:
- Small file: ~300 characters, 3-4 bullets.
- Medium file: ~600 characters, 5-7 bullets.
- Large file: ~1200 characters, 8-12 bullets.
Content of bullets — describe in order:
- What the file provides (capability groups, not individual symbol names).
- How groups of related functions/types behave and what algorithms they use.
- Design decisions: data representations, fallback behaviors, degenerate-path handling.
- Integration notes: what kind of callers use this (runtime, tests, Lua bindings, tools).
Do NOT list individual function names, struct names, or method names. Describe capabilities and behaviors. The reader can see declarations — the //! block explains WHY this file exists and HOW its pieces relate.
Reference example (src/math/geometry.rs):
//! - Provides standalone 2D geometry algorithms over scalar coordinates and flat vertex arrays.
//! - Basic helpers compute angle between points, circle point containment, circle-circle overlap, line-circle intersections, and segment-circle intersections.
//! - Polygon helpers compute signed area, centroid with arithmetic-mean fallback for degenerate polygons, point-in-polygon by ray casting, and convex hull by Andrew monotone chain.
//! - Functions favor simple tuples and slices so they can be used from runtime code, tests, and Lua bindings without owning heavier geometry types.
//! - Degenerate paths generally return conservative values such as empty result sets, arithmetic centroids, or false intersections instead of panicking.
Struct (///)
One line: role in the system + which other type uses it.
Every field — pub and private — one /// line: what it holds + range/units/invariant when non-obvious.
Enum (///)
One line: purpose. Every variant: one line — when it applies.
Impl blocks (///)
Every impl Trait for Type block gets one /// line above it: what the trait provides for this type.
Plain impl Type { } blocks (inherent impl): if a /// comment already exists, keep and improve it. When adding new impl blocks, a /// comment is optional but recommended when the block groups a distinct set of related methods (e.g. constructors, getters, serialization helpers). Never remove an existing /// on a plain impl.
Methods and functions (///)
One line only. State what it does AND what it returns, including edge cases inline.
No # Arguments, no # Returns, no # Errors sections.
Single compact example (only one):
/// Decodes file and returns chunk decoder; returns error on open/decode failure.
Forbidden Phrases in Rustdoc
- No AI-generated prose: "returns a fully initialised instance", "the insertion is O(1) amortised", "this accessor incurs no allocation", "call it freely in hot paths".
- No synonym inflation: "alias for
foo()", "alias of foo()", "shorthand for", "convenience wrapper".
- No placeholders: "placeholder", "empty placeholder", "tombstone", "replacement value".
- No
incurs no allocation, O(1), amortised — these are implementation details, not user-facing docs.
- No
returns when opening a function doc. Use imperative: "Return" or "Read" or "Parse" instead of "Returns".
Rules (tool-aligned)
- Enforced by tools: every
.rs file has //! module doc.
- Enforced by tools: every
pub item has /// doc.
- Tooling parses plain
/// and //! text; no structured rustdoc sections are required by scanners.
- Keep comments compact one-liners when possible to minimize token cost for AI agents.
- For regular files, avoid
# Arguments / # Returns / # Errors sections unless a prompt explicitly requires them.
- Every doc comment must be a concrete technical fact, not a sentence structure template.
Lua API files (src/lua_api/)
These files expose lurek.* to Lua. Contract source of truth is tools/validate/validate_lua_api.py.
File level (//!)
Use canonical header format expected by validator.
- Format:
//! `lurek.<module>` -- <concrete description>.
- Example:
//! `lurek.audio` -- Audio bindings for source playback, bus routing, and DSP controls.
Structs, fields, enums, helpers in src/lua_api/
Same compact one-liner rule as all other Rust files. No # Fields block.
- Example struct line:
/// Lua-side handle for an audio source registered in `Mixer`.
- Example field line:
/// Slot-map key identifying this source in `Mixer`.
- This applies to private helpers too: every helper function gets one concrete
/// line.
Docs for Lua-registered calls only
Only methods/functions actually registered to Lua (methods.add_method, methods.add_method_mut, methods.add_function, tbl.set(... create_function ...)) must use verbose tagged markers.
- Required shape above each registered call:
- One summary
/// line.
- Zero or more
@param lines when method takes Lua args.
- At least one
@return line always.
- Example:
/// Sets playback volume. + /// @param | vol | number | Volume multiplier, clamped to >= 0.0. + /// @return | nil | No value is returned.
Marker format (required)
/// @param | <lua_name> | <lua_type> | <description>
/// @return | <lua_type> | <description>
lua_type values: number, integer, string, boolean, nil, Source, Bus, table, etc.
- Every registered Lua call must have at least one
@return marker.
- Keep
@param / @return descriptions to one line — they appear verbatim in generated API docs.
- Do not mix marker styles: never use
@param name : type in files validated by validate_lua_api.py.
Validator-enforced checks (must stay true)
- Header exists and matches
//! `lurek.<module>` ....
pub fn register(...) exists and has canonical Lua/LuaTable signature.
- No rustdoc sections
/// # Parameters or /// # Returns.
- Tagged docs use exact pipe format:
/// @param | name | type | description
/// @return | type | description
- No
@return | any | ....
- No optional/union return types.
Forbidden in src/lua_api/
/// # Parameters
/// # Returns
@return | any | ...
- Optional/union return types like
number?, number, nil, Type|nil
Companion File Index
References
- src/
- docs/specs/
- tests/rust/unit/
- tests/lua/
Source: Lurek2D/lurek_2d — distributed by TomeVault.
1---2name: rust-coding-23description: Load this skill when writing or reviewing Rust engine code. It owns safe Rust conventions, error patterns, module structure, and idiomatic style. Skip it for Lua scripts, CAG files, or docs. Use when this capability is needed.4---5# rust-coding67## Mission8- Own safe, idiomatic Rust patterns for the engine codebase.910## When To Load11- Write Rust code.12- Review Rust code.13- Refactor Rust modules.1415## When To Skip16- Lua scripts.17- CAG files.18- Docs-only work.1920## Domain Knowledge21- mod.rs in src/ must contain only `pub mod`, `pub use`, doc comments, and `#[allow]` attributes. Definitions belong in sibling files.22- No `#[cfg(test)]` blocks in src/. Unit tests for private code go in `tests/rust/unit/<module>_tests.rs`, named `<module>_tests.rs` not `<module>_test.rs`.23- When external tests need a private seam, prefer `pub(crate)` over `pub` and keep the exposed item narrow. Treat it as a testability boundary, not a public API expansion.24- If a `pub(crate)` seam exists only for tests, keep its doc comment explicit about that intent so future refactors do not promote it accidentally.25- `src/lua_api/*_api.rs` must stay thin: `LuaUserData` impls, `add_methods`, registration, and type conversions only. Business logic belongs in `src/<module>/`.26- Never hold `borrow_mut()` or `RefCell::borrow_mut()` across a Lua callback invocation. The pattern is: extract all needed values while holding the borrow, release it, then invoke Lua.27- SharedState access rule: `{ let guard = state.borrow(); let val = guard.field.clone(); } /* borrow released */ call_lua(val)`. The guard must be dropped before any call that might re-enter Rust.28- Pinned library versions: mlua 0.9, wgpu 22, winit 0.30, rapier2d 0.32, rodio 0.17, fontdue 0.9. Do not bump without explicit authorization; each bump needs wgpu/winit API adjustments.29- When a Rust change touches public types or functions visible through `lurek.*`, run `tools/python.cmd tools/validate/validate_lua_api.py` to catch shape drift in generated docs.30- Public changes must update `docs/specs/<module>.md` in the same commit. Compiler green does not mean the task is done.31- Use `?` for propagation but never let it cross a callback boundary silently. Closures passed to mlua must return `mlua::Result`; inner `?` should map errors before they reach Lua with a clear message.32- Keep `unsafe` blocks small, one-purpose, and accompanied by a `// SAFETY:` comment that explains the invariant. No `unsafe` for convenience when a safe alternative exists.33- Prefer explicit module imports over glob imports. Glob imports make it impossible to grep what the file actually depends on during refactors.34- Tests in `tests/rust/unit/` name their test functions `test_<behavior>_<condition>`. Keep each test assertion to one failure mode so failing tests name the problem immediately.3536## Rustdoc Comment Standards — AI-First, Compact3738Comments exist for AI agents reading the code. Goal: maximum signal per token. No prose, no repetition, no filler.3940---4142### Regular Rust files (`src/` excluding `src/lua_api/`)4344**What requires a doc comment**45Every one of the following lines must have a `///` comment directly above it — no exceptions, no qualifications:46- `pub fn`, `pub async fn`47- `fn` (every private function, without exception)48- `pub struct`, `struct`49- `pub enum`, `enum`50- `pub mod` (every occurrence, in any file)51- `pub type`, `pub const`, `pub static`52- `impl Trait for Type` — one `///` line above: what the trait provides for this type53- Every field inside a struct — pub and private — one `///` line each54- Every variant inside an enum — one `///` line each5556Plain `impl Type { }` (inherent impl): keep existing `///` comments and improve them. Add `///` when the block groups distinct functionality. Never remove existing `///` on impl blocks.5758**File level (`//!`)**59Use bullet-point format: each line starts with `//! - `. Proportional to file size:60- Small file: ~300 characters, 3-4 bullets.61- Medium file: ~600 characters, 5-7 bullets.62- Large file: ~1200 characters, 8-12 bullets.6364Content of bullets — describe in order:651. What the file provides (capability groups, not individual symbol names).662. How groups of related functions/types behave and what algorithms they use.673. Design decisions: data representations, fallback behaviors, degenerate-path handling.684. Integration notes: what kind of callers use this (runtime, tests, Lua bindings, tools).6970**Do NOT list individual function names, struct names, or method names.** Describe capabilities and behaviors. The reader can see declarations — the `//!` block explains WHY this file exists and HOW its pieces relate.7172Reference example (`src/math/geometry.rs`):73> //! - Provides standalone 2D geometry algorithms over scalar coordinates and flat vertex arrays.74> //! - Basic helpers compute angle between points, circle point containment, circle-circle overlap, line-circle intersections, and segment-circle intersections.75> //! - Polygon helpers compute signed area, centroid with arithmetic-mean fallback for degenerate polygons, point-in-polygon by ray casting, and convex hull by Andrew monotone chain.76> //! - Functions favor simple tuples and slices so they can be used from runtime code, tests, and Lua bindings without owning heavier geometry types.77> //! - Degenerate paths generally return conservative values such as empty result sets, arithmetic centroids, or false intersections instead of panicking.7879**Struct (`///`)**80One line: role in the system + which other type uses it.81Every field — pub and private — one `///` line: what it holds + range/units/invariant when non-obvious.8283**Enum (`///`)**84One line: purpose. Every variant: one line — when it applies.8586**Impl blocks (`///`)**87Every `impl Trait for Type` block gets one `///` line above it: what the trait provides for this type.88Plain `impl Type { }` blocks (inherent impl): if a `///` comment already exists, keep and improve it. When adding new impl blocks, a `///` comment is optional but recommended when the block groups a distinct set of related methods (e.g. constructors, getters, serialization helpers). Never remove an existing `///` on a plain impl.8990**Methods and functions (`///`)**91One line only. State what it does AND what it returns, including edge cases inline.92No `# Arguments`, no `# Returns`, no `# Errors` sections.9394Single compact example (only one):95- `/// Decodes file and returns chunk decoder; returns error on open/decode failure.`9697**Forbidden Phrases in Rustdoc**98- No AI-generated prose: "returns a fully initialised instance", "the insertion is O(1) amortised", "this accessor incurs no allocation", "call it freely in hot paths".99- No synonym inflation: "alias for `foo()`", "alias of `foo()`", "shorthand for", "convenience wrapper".100- No placeholders: "placeholder", "empty placeholder", "tombstone", "replacement value".101- No `incurs no allocation`, `O(1)`, `amortised` — these are implementation details, not user-facing docs.102- No `returns` when opening a function doc. Use imperative: "Return" or "Read" or "Parse" instead of "Returns".103104**Rules (tool-aligned)**105- Enforced by tools: every `.rs` file has `//!` module doc.106- Enforced by tools: every `pub` item has `///` doc.107- Tooling parses plain `///` and `//!` text; no structured rustdoc sections are required by scanners.108- Keep comments compact one-liners when possible to minimize token cost for AI agents.109- For regular files, avoid `# Arguments` / `# Returns` / `# Errors` sections unless a prompt explicitly requires them.110- Every doc comment must be a concrete technical fact, not a sentence structure template.111112---113114### Lua API files (`src/lua_api/`)115116These files expose `lurek.*` to Lua. Contract source of truth is `tools/validate/validate_lua_api.py`.117118**File level (`//!`)**119Use canonical header format expected by validator.120- Format: ``//! `lurek.<module>` -- <concrete description>``.121- Example: ``//! `lurek.audio` -- Audio bindings for source playback, bus routing, and DSP controls.``122123124**Structs, fields, enums, helpers in `src/lua_api/`**125Same compact one-liner rule as all other Rust files. No `# Fields` block.126- Example struct line: ``/// Lua-side handle for an audio source registered in `Mixer`.``127- Example field line: ``/// Slot-map key identifying this source in `Mixer`.``128- This applies to private helpers too: every helper function gets one concrete `///` line.129130131**Docs for Lua-registered calls only**132Only methods/functions actually registered to Lua (`methods.add_method`, `methods.add_method_mut`, `methods.add_function`, `tbl.set(... create_function ...)`) must use verbose tagged markers.133- Required shape above each registered call:134- One summary `///` line.135- Zero or more `@param` lines when method takes Lua args.136- At least one `@return` line always.137- Example: ``/// Sets playback volume.`` + ``/// @param | vol | number | Volume multiplier, clamped to >= 0.0.`` + ``/// @return | nil | No value is returned.``138139140**Marker format (required)**141- `/// @param | <lua_name> | <lua_type> | <description>`142- `/// @return | <lua_type> | <description>`143- `lua_type` values: `number`, `integer`, `string`, `boolean`, `nil`, `Source`, `Bus`, `table`, etc.144- Every registered Lua call must have at least one `@return` marker.145- Keep `@param` / `@return` descriptions to one line — they appear verbatim in generated API docs.146- Do not mix marker styles: never use `@param name : type` in files validated by `validate_lua_api.py`.147148**Validator-enforced checks (must stay true)**149- Header exists and matches ``//! `lurek.<module>` ...``.150- `pub fn register(...)` exists and has canonical Lua/LuaTable signature.151- No rustdoc sections `/// # Parameters` or `/// # Returns`.152- Tagged docs use exact pipe format:153 - `/// @param | name | type | description`154 - `/// @return | type | description`155- No `@return | any | ...`.156- No optional/union return types.157158**Forbidden in `src/lua_api/`**159- `/// # Parameters`160- `/// # Returns`161- `@return | any | ...`162- Optional/union return types like `number?`, `number, nil`, `Type|nil`163## Companion File Index164- None.165166## References167- src/168- docs/specs/169- tests/rust/unit/170- tests/lua/171172---173> Source: [Lurek2D/lurek_2d](https://github.com/Lurek2D/lurek_2d) — distributed by [TomeVault](https://tomevault.io).174<!-- tomevault:4.0:skill_md:2026-06-16 -->