development
name: rust-coding-standard description: Rust coding standards for the onde crate. Covers error handling, naming, async patterns, platform gating, clippy compliance, and cross-platform SDK conventions. Apply to all Rust edits. allowed-tools: Read, Write, Edit, Glob, Grep
user-invocable: false
Rust Coding Standard
Rules for writing Rust in the onde crate. These are traps to avoid, not maps to follow.
Correctness First
Prioritize correctness and clarity. Performance is secondary unless explicitly stated. Complete, mostly-correct code beats perfect code that doesn't solve the problem.
Error Handling
- Propagate errors with
?. Never call.unwrap()or.expect()outside of#[cfg(test)]code. - Never silently discard errors with
let _ =on fallible operations:- Propagate with
?when the caller should handle it. - Use
log::warn!orlog::error!when you need to ignore but want visibility. - Use
matchorif let Err(e)when custom recovery logic is needed.
- Propagate with
- Use
anyhowfor internal application errors inengine.rs. - Use
thiserrorfor theInferenceErrorenum — it crosses the FFI boundary via UniFFI. - Be careful with indexing operations that may panic if out of bounds. Prefer
.get()and safe alternatives.
Naming
- Use full words for all variable and parameter names. No single-letter names, no abbreviations (
environmentnotenv,clientnotc,messagenotmsg). snake_casefor functions and variables,CamelCasefor types,SCREAMING_SNAKE_CASEfor constants.- Model constants live in
src/inference/models.rs. Prefix HuggingFace repo IDs with the org name:BARTOWSKI_QWEN25_....
Comments
- Comments explain why, never what. Do not write comments that summarize or re-describe the code.
- Doc comments (
///) on public items are fine and encouraged — they explain the contract, not the implementation. - Do not add organizational separators like
// --- Helper functions ---unless they are already established in the file's style.
Module Structure
- No
mod.rsfor new modules. Usesrc/some_module.rsinstead ofsrc/some_module/mod.rs. Exception:inference/mod.rsalready exists and is the intentional module root — don't move it. - Prefer implementing functionality in existing files unless it's a genuinely new logical component. Avoid creating many small single-purpose files.
Async
- Runtime is
tokio. All async functions usetokio::sync::Mutex, notstd::sync::Mutex. - Use variable shadowing to scope
.clone()calls inside async closures, minimizing borrow lifetimes:
tokio::spawn({
let engine = engine.clone();
async move {
engine.do_work().await;
}
});
Logging
- Use
logcrate macros:log::debug!,log::info!,log::warn!,log::error!. - No
println!oreprintln!in library code. Ever. - Log at
debugfor routine operations,infofor lifecycle events (model loaded/unloaded),warnfor recoverable issues,errorfor failures.
Platform Gating
- Use
#[cfg(target_os = "...")]blocks. MatchCargo.toml's target-conditional dependency sections. - When a function is only used on specific platforms, gate the function itself — not just the call site. This prevents dead-code warnings on other targets.
- When an import is only used on specific platforms, gate the
usestatement separately. Do not put platform-only imports inside a shareduseblock. - The supported
target_osvalues are:macos,ios,tvos,windows,linux,android. - ISQ model loading is
#[cfg(target_os = "macos")]only. Gate theIsqvariant, its match arms, and its imports (IsqBits,TextModelBuilder) to macOS.
Clippy Compliance
All code must pass cargo clippy --all-targets -- -D warnings. Common traps:
&PathBufin function parameters → use&Pathinstead.unwrap_or_else(Type::default)→ useunwrap_or_default().filter_map(|x| { ... Some(y) })where every path returnsSome→ usemap..map(|x| f(x))wherefis a plain function → use.map(f).- Redundant type annotations in closures:
.map(|c: &String| c.trim())→.map(|c| c.trim()). - Unused imports/variants on platform-gated code → gate the import/variant to the same platform.
Types for FFI
usize/isizeare not FFI-safe. Useu64/i64in any type that crosses the UniFFI boundary.impl Into<String>is not FFI-safe. Use concreteStringin exported signatures.- All UniFFI-exported types go in
src/inference/types.rs. The FFI wrapper lives insrc/inference/ffi.rs. ChatEngine(engine.rs) owns Rust-idiomatic logic.OndeChatEngine(ffi.rs) is the thin FFI wrapper. Never put#[uniffi::export]onChatEngine.
Re-exports
mistralrs, hf_hub, and mistralrs_core are re-exported from lib.rs for downstream Rust consumers. Keep these in sync with what's actually available per platform.
Testing
- Unit tests go in a
#[cfg(test)] mod testsblock at the bottom of the file they test. - Use
#[tokio::test]for async tests. - Tests may use
.unwrap()and.expect()— the panics are the assertions. - Test names describe the scenario:
engine_send_without_model_errors, nottest_sendortest_1. - Run
cargo test --libto skip integration tests that require model downloads.
Adding a New Model
This is a common task with a specific checklist:
- Add
pub constentries insrc/inference/models.rsfor repo ID, GGUF filename, andTOK_MODEL_ID. - Add the repo ID to
SUPPORTED_MODELS. - Add a
SupportedModelInfoentry toSUPPORTED_MODEL_INFOwithexpected_size_bytesfrom the HF API. - Add a constructor to
GgufModelConfiginengine.rs(followqwen25_1_5bpattern). - Export a free function in
ffi.rs(followqwen25_1_5b_configpattern).
Build Verification
Always run cargo check before returning work. If code does not compile, fix it — do not return with TODOs. On macOS, also run cargo clippy --all-targets -- -D warnings to catch lint issues before CI does.