|
1
|
--- |
|
2
|
name: rust-coding-standard |
|
3
|
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. |
|
4
|
allowed-tools: Read, Write, Edit, Glob, Grep |
|
5
|
user-invocable: false |
|
6
|
--- |
|
7
|
|
|
8
|
# Rust Coding Standard |
|
9
|
|
|
10
|
Rules for writing Rust in the `onde` crate. These are traps to avoid, not maps to follow. |
|
11
|
|
|
12
|
--- |
|
13
|
|
|
14
|
## Correctness First |
|
15
|
|
|
16
|
Prioritize correctness and clarity. Performance is secondary unless explicitly stated. Complete, mostly-correct code beats perfect code that doesn't solve the problem. |
|
17
|
|
|
18
|
## Error Handling |
|
19
|
|
|
20
|
- Propagate errors with `?`. Never call `.unwrap()` or `.expect()` outside of `#[cfg(test)]` code. |
|
21
|
- Never silently discard errors with `let _ =` on fallible operations: |
|
22
|
- Propagate with `?` when the caller should handle it. |
|
23
|
- Use `log::warn!` or `log::error!` when you need to ignore but want visibility. |
|
24
|
- Use `match` or `if let Err(e)` when custom recovery logic is needed. |
|
25
|
- Use `anyhow` for internal application errors in `engine.rs`. |
|
26
|
- Use `thiserror` for the `InferenceError` enum — it crosses the FFI boundary via UniFFI. |
|
27
|
- Be careful with indexing operations that may panic if out of bounds. Prefer `.get()` and safe alternatives. |
|
28
|
|
|
29
|
## Naming |
|
30
|
|
|
31
|
- Use full words for all variable and parameter names. No single-letter names, no abbreviations (`environment` not `env`, `client` not `c`, `message` not `msg`). |
|
32
|
- `snake_case` for functions and variables, `CamelCase` for types, `SCREAMING_SNAKE_CASE` for constants. |
|
33
|
- Model constants live in `src/inference/models.rs`. Prefix HuggingFace repo IDs with the org name: `BARTOWSKI_QWEN25_...`. |
|
34
|
|
|
35
|
## Comments |
|
36
|
|
|
37
|
- Comments explain **why**, never **what**. Do not write comments that summarize or re-describe the code. |
|
38
|
- Doc comments (`///`) on public items are fine and encouraged — they explain the contract, not the implementation. |
|
39
|
- Do not add organizational separators like `// --- Helper functions ---` unless they are already established in the file's style. |
|
40
|
|
|
41
|
## Module Structure |
|
42
|
|
|
43
|
- **No `mod.rs` for new modules.** Use `src/some_module.rs` instead of `src/some_module/mod.rs`. Exception: `inference/mod.rs` already exists and is the intentional module root — don't move it. |
|
44
|
- Prefer implementing functionality in existing files unless it's a genuinely new logical component. Avoid creating many small single-purpose files. |
|
45
|
|
|
46
|
## Async |
|
47
|
|
|
48
|
- Runtime is `tokio`. All async functions use `tokio::sync::Mutex`, not `std::sync::Mutex`. |
|
49
|
- Use variable shadowing to scope `.clone()` calls inside async closures, minimizing borrow lifetimes: |
|
50
|
|
|
51
|
```rust |
|
52
|
tokio::spawn({ |
|
53
|
let engine = engine.clone(); |
|
54
|
async move { |
|
55
|
engine.do_work().await; |
|
56
|
} |
|
57
|
}); |
|
58
|
``` |
|
59
|
|
|
60
|
## Logging |
|
61
|
|
|
62
|
- Use `log` crate macros: `log::debug!`, `log::info!`, `log::warn!`, `log::error!`. |
|
63
|
- No `println!` or `eprintln!` in library code. Ever. |
|
64
|
- Log at `debug` for routine operations, `info` for lifecycle events (model loaded/unloaded), `warn` for recoverable issues, `error` for failures. |
|
65
|
|
|
66
|
## Platform Gating |
|
67
|
|
|
68
|
- Use `#[cfg(target_os = "...")]` blocks. Match `Cargo.toml`'s target-conditional dependency sections. |
|
69
|
- 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. |
|
70
|
- When an import is only used on specific platforms, gate the `use` statement separately. Do not put platform-only imports inside a shared `use` block. |
|
71
|
- The supported `target_os` values are: `macos`, `ios`, `tvos`, `windows`, `linux`, `android`. |
|
72
|
- ISQ model loading is `#[cfg(target_os = "macos")]` only. Gate the `Isq` variant, its match arms, and its imports (`IsqBits`, `TextModelBuilder`) to macOS. |
|
73
|
|
|
74
|
## Clippy Compliance |
|
75
|
|
|
76
|
All code must pass `cargo clippy --all-targets -- -D warnings`. Common traps: |
|
77
|
|
|
78
|
- `&PathBuf` in function parameters → use `&Path` instead. |
|
79
|
- `unwrap_or_else(Type::default)` → use `unwrap_or_default()`. |
|
80
|
- `filter_map(|x| { ... Some(y) })` where every path returns `Some` → use `map`. |
|
81
|
- `.map(|x| f(x))` where `f` is a plain function → use `.map(f)`. |
|
82
|
- Redundant type annotations in closures: `.map(|c: &String| c.trim())` → `.map(|c| c.trim())`. |
|
83
|
- Unused imports/variants on platform-gated code → gate the import/variant to the same platform. |
|
84
|
|
|
85
|
## Types for FFI |
|
86
|
|
|
87
|
- `usize` / `isize` are not FFI-safe. Use `u64` / `i64` in any type that crosses the UniFFI boundary. |
|
88
|
- `impl Into<String>` is not FFI-safe. Use concrete `String` in exported signatures. |
|
89
|
- All UniFFI-exported types go in `src/inference/types.rs`. The FFI wrapper lives in `src/inference/ffi.rs`. |
|
90
|
- `ChatEngine` (engine.rs) owns Rust-idiomatic logic. `OndeChatEngine` (ffi.rs) is the thin FFI wrapper. Never put `#[uniffi::export]` on `ChatEngine`. |
|
91
|
|
|
92
|
## Re-exports |
|
93
|
|
|
94
|
`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. |
|
95
|
|
|
96
|
## Testing |
|
97
|
|
|
98
|
- Unit tests go in a `#[cfg(test)] mod tests` block at the bottom of the file they test. |
|
99
|
- Use `#[tokio::test]` for async tests. |
|
100
|
- Tests may use `.unwrap()` and `.expect()` — the panics are the assertions. |
|
101
|
- Test names describe the scenario: `engine_send_without_model_errors`, not `test_send` or `test_1`. |
|
102
|
- Run `cargo test --lib` to skip integration tests that require model downloads. |
|
103
|
|
|
104
|
## Adding a New Model |
|
105
|
|
|
106
|
This is a common task with a specific checklist: |
|
107
|
|
|
108
|
1. Add `pub const` entries in `src/inference/models.rs` for repo ID, GGUF filename, and `TOK_MODEL_ID`. |
|
109
|
2. Add the repo ID to `SUPPORTED_MODELS`. |
|
110
|
3. Add a `SupportedModelInfo` entry to `SUPPORTED_MODEL_INFO` with `expected_size_bytes` from the HF API. |
|
111
|
4. Add a constructor to `GgufModelConfig` in `engine.rs` (follow `qwen25_1_5b` pattern). |
|
112
|
5. Export a free function in `ffi.rs` (follow `qwen25_1_5b_config` pattern). |
|
113
|
|
|
114
|
## Build Verification |
|
115
|
|
|
116
|
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. |