- name
- rust
- description
- Strict set of rules in terms of codebase development, design patterns, and best practices. Use when the user wants to develop a new feature or refactor existing code.
## Principles
Priority: **Correctness > Safety > Readability > Performance**
- **Idiomatic Rust.** Follow standard library conventions. If the stdlib does it one way, do it that way.
- **Leverage the type system.** Encode invariants in types, not runtime checks. Make illegal states unrepresentable.
- **Domain-driven design.** Name everything in the language of the problem, not the implementation.
- **Reuse existing libraries and frameworks** when possible.
## Safety rules
Adapted from [The Power of Ten](https://en.wikipedia.org/wiki/The_Power_of_10:_Rules_for_Developing_Safety-Critical_Code) (Holzmann) for Rust.
1. **Simple control flow.** No nested if-else, no nested loops. Split into single-purpose functions. Max one level of branching per function body.
2. **Short functions.** No function longer than ~60 lines. If it's too long, decompose it.
3. **Fixed loop bounds.** All loops must have a provable upper bound. Prefer iterators (`for x in collection`) over manual `while i < len` with index arithmetic.
4. **Minimal allocation.** Prefer `&str` over `String`, slices over `Vec`, borrows over clones. Allocate only when you must own.
5. **Assertions at boundaries.** Use `debug_assert!` for invariants within functions. Validate inputs at public API boundaries with `Result`/`Option`, not panics.
6. **Smallest possible scope.** Variables, types, and functions should be visible only where needed. Prefer module-private by default; add `pub` only when required.
7. **Handle all return values.** Never discard `Result`. Use `?` propagation or explicit handling. Annotate intentional ignores with `let _ =`.
8. **Macros sparingly.** Prefer generics, traits, and enums over procedural macros. Macros obscure control flow and complicate debugging.
9. **Domain types over raw primitives.** Wrap repetitive low-level operations in a named type instead of scattering the same logic across functions.
```rust
// bad — raw index arithmetic repeated everywhere
let mut i = 0;
while i < bytes.len() {
if bytes[i] == target { return i; }
i += 1;
}
// good — domain cursor encapsulates iteration
let mut scanner = Scanner::new(bytes);
while let Some(b) = scanner.peek() {
if b == target { return scanner.position(); }
scanner.advance();
}
```
10. **Zero warnings.** Code must compile with `#[deny(warnings)]` cleanly. Run `cargo clippy` and address all lints.
11. **Use `#[expect(lint, reason = "...")]` instead of `#[allow]`.** `#[expect]` warns when the suppression becomes unnecessary, preventing stale silencing. Always include a `reason` string. *(M-LINT-OVERRIDE-EXPECT)*
12. **Panics mean stop the program.** Panics are not exceptions. Never use `panic!`, `unwrap()`, or `unreachable!()` for recoverable errors — use `Result`. `expect()` is acceptable only for proven invariants with a descriptive message. *(M-PANIC-IS-STOP)*
13. **`unsafe` only when required by 3rd-party libraries** (e.g. PyO3 macros). No other reasons to write unsafe code.
## Naming conventions
*Sources: Rust API Guidelines C-CONV, C-GETTER; Microsoft M-CONCISE-NAMES*
- **Conversion prefixes follow ownership semantics:**
- `as_` — cheap reference-to-reference (no allocation, no copy)
- `to_` — expensive conversion, may allocate (e.g. `to_string()`)
- `into_` — consumes self, returns owned value
- **No `get_` prefix on getters.** Use `fn name(&self) -> &str`, not `fn get_name()`.
- **Implement `From<T>`, never `Into<T>`.** The blanket impl gives you `Into` for free.
- **Concise type names.** Avoid hollow suffixes: `Service`, `Manager`, `Factory`, `Handler`, `Processor`. If the name needs a suffix, the type does too much.
- **Named constants over magic literals.** Every literal with domain meaning gets a `const` with a doc comment. No bare numbers, bytes, or strings in logic.
```rust
// bad
if b == b'\\' { i += 2; }
// good
const ESCAPE_BYTE: u8 = b'\\';
if b == ESCAPE_BYTE { scanner.skip_escaped(); }
```
## Error handling
*Sources: Microsoft M-APP-ERROR, M-ERRORS-CANONICAL-STRUCTS*
- **`thiserror` for library crates, `anyhow`/`eyre` for application crates.** Libraries expose structured errors; apps just need context chains.
- **Error messages: lowercase, no trailing punctuation.** Matches `std` convention for composable `.context()` chains.
- **Use `?` propagation everywhere.** Avoid `match` on `Result` when `?` + `.map_err()` suffices.
- **Never `unwrap()` in non-test code.** Use `expect("reason")` only for proven invariants.
- **Canonical error struct pattern:**
```rust
#[derive(Debug, thiserror::Error)]
pub enum ParseError {
#[error("invalid token at position {position}")]
InvalidToken { position: usize, token: char },
#[error("unexpected end of input")]
UnexpectedEof,
#[error(transparent)]
Io(#[from] std::io::Error),
}
```
## Domain-driven design
Adapted from [Domain-Driven Design](https://martinfowler.com/bliki/DomainDrivenDesign.html) (Evans/Fowler) for Rust.
### Ubiquitous language
Name types, functions, and modules in the language of the problem domain, not the implementation.
```rust
// bad — describes implementation mechanics
fn find_end_offset(s: &str) -> Option<usize>
fn check_string(s: &str) -> bool
// good — describes domain concepts
fn PaymentResult::validate(invoice: &Invoice) -> Option<PaymentResult>
fn ClassName::is_tailwind(token: &str) -> bool
```
### Value objects as structs
Domain values without identity are structs. Functions take `&Struct` and return new structs — no mutation through output parameters.
```rust
// bad — caller provides mutable buffer
fn process(input: &Config, out: &mut String)
// good — function returns a value object
fn process(input: &Config) -> ProcessResult
```
### Enums for closed domain rules
When a domain has a fixed set of variants or checks, use an enum — not trait objects, not loose functions.
```rust
// bad — scattered functions, no unifying type
fn is_not_empty(s: &str) -> bool { ... }
fn starts_with_letter(s: &str) -> bool { ... }
// good — enum variants are self-documenting and composable
enum ValidationRule { NonEmpty, StartsWithLetter, ContainsHyphen }
impl ValidationRule {
fn passes(self, input: &str) -> bool { match self { ... } }
}
const RULES: &[ValidationRule] = &[ValidationRule::NonEmpty, ...];
```
### Traits for open abstractions
Use traits when behavior needs to be extended by future implementations. Start with the trait, then implement concrete types.
```rust
// bad — parallel standalone functions
fn run_git_cmd() -> Output { ... }
fn run_uv_cmd() -> Output { ... }
// good — shared trait, separate implementations
trait ExternalCommand {
fn execute(&self) -> Result<Output>;
}
impl ExternalCommand for Git { ... }
impl ExternalCommand for Uv { ... }
```
*See also [Service & middleware](#service--middleware) and [Trait object plugin](#trait-object-plugin) in Ecosystem patterns.*
### Modules as bounded contexts
Each Rust module is a bounded context. Types and functions within a module share a domain model; the module boundary is the public API. Keep internal helpers private.
### Sans-I/O: separate protocol logic from transport
*Source: [sans-io.readthedocs.io](https://sans-io.readthedocs.io/how-to-sans-io.html)*
Protocol logic (parsing, validation, state machines, data transformation) must be pure functions or types that take data in and return data out — no sockets, no channels, no async, no file handles. I/O operations (network, channels, disk) live in a thin outer layer that calls the protocol layer.
This makes protocol logic testable without standing up real infrastructure, reusable across different I/O backends (tokio, crossbeam, sync), and composable across boundaries (Rust ↔ Python).
```rust
// bad — protocol parsing entangled with channel I/O
impl SlotSend {
fn __call__(&self, py, event: &PyDict) -> PyResult<...> {
let type_val: String = event.get_item("type")?.extract()?;
match type_val.as_str() {
"http.response.start" => {
let status = event.get_item("status")?.extract()?;
*self.status.lock() = Some(status);
// parse headers here too...
}
"http.response.body" => {
let body = event.get_item("body")?.extract()?;
self.outbound_tx.send(OutboundSlot { ... })?; // I/O mixed in
self.body_tx.send(body)?; // I/O mixed in
}
}
}
}
// good — protocol layer is a pure function, I/O layer calls it
enum SendEvent {
Start { status: u16, headers: Vec<(Bytes, Bytes)> },
Body { data: Bytes, more_body: bool },
}
fn parse_send_event(event: &Bound<'_, PyDict>) -> PyResult<SendEvent> {
// pure — no channels, no async, testable with synthetic PyDicts
}
impl SlotSend {
fn __call__(&self, py, event: &PyDict) -> PyResult<...> {
let parsed = parse_send_event(event)?; // protocol
self.dispatch(parsed) // I/O
}
}
```
The same principle applies to request classification:
```rust
// bad — routing decision mixed with hyper I/O
async fn handle(self, req: Request<Incoming>) -> Response<...> {
if req.uri().path() == "/_health/alive" {
return json_response(HEALTH_ALIVE); // mixed: decision + response construction
}
if is_websocket_upgrade(&req) {
return self.dispatch.dispatch_ws(req).await; // mixed: decision + dispatch
}
// ... semaphore, timeout, dispatch ...
}
// good — classification is a pure function
enum RequestKind {
Probe(ProbeKind),
WebSocket,
Http,
}
fn classify(path: &str, headers: &HeaderMap) -> RequestKind {
// pure — no async, no Response construction, testable with strings
}
async fn handle(self, req: Request<Incoming>) -> Response<...> {
match classify(req.uri().path(), req.headers()) {
RequestKind::Probe(kind) => probe_response(kind),
RequestKind::WebSocket => self.dispatch.dispatch_ws(req).await,
RequestKind::Http => self.dispatch_http(req).await,
}
}
```
**Rule of thumb:** if a function touches both data transformation AND a channel/socket/file, split it. The data transformation half is the protocol layer; the channel/socket half is the I/O layer. The protocol layer should be testable with `#[test]` using synthetic inputs — no `#[tokio::test]`, no channels, no `Python::attach`.
## API design
*Sources: Microsoft M-INIT-BUILDER, M-IMPL-ASREF, M-IMPL-IO, M-AVOID-WRAPPERS; Rust API Guidelines C-COMMON-TRAITS*
- **Builder pattern for complex initialization.** When a type has 4+ optional configuration fields, provide a builder instead of a constructor with many parameters.
- **Accept `impl AsRef<str>` / `impl AsRef<Path>`** over concrete `&str`/`String`/`&Path` in function params when callers may have either type.
- **Accept `impl Read` / `impl Write` for I/O functions.** Decouples logic from concrete I/O sources — enables testing with `Cursor<Vec<u8>>`.
- **Avoid smart pointers in public APIs.** Don't expose `Arc<Mutex<T>>`, `Box<T>`, `Rc<T>` — let callers choose their wrapping strategy.
- **Eagerly implement common traits:** `Debug`, `Clone`, `PartialEq`, `Default` on all public types. *(C-COMMON-TRAITS)*
- **All public types must implement `Debug`.** No exceptions. *(M-PUBLIC-DEBUG)*
- **Avoid unnecessary `Copy`.** Do not derive or implement `Copy` unless the type genuinely benefits from implicit copy semantics. Prefer `Clone` with explicit `.clone()` so copies are visible and intentional.
## Code patterns
### Return values, don't mutate
Functions return domain types instead of writing into `&mut` parameters. This makes data flow explicit and enables composition via `.map()`, `.fold()`, iterators.
```rust
// bad — mutation hides data flow
fn transform(input: &str, out: &mut Vec<String>)
// good — return value makes flow explicit
fn transform(input: &str) -> Vec<TransformResult>
```
### Flat validation with early returns
Split complex validation into a scanning step and a checking step. Each is its own function. No nesting beyond one level.
```rust
// bad — nested ifs, multiple concerns in one block
if b == CLOSE {
if i + 1 < len && bytes[i + 1] == CLOSE {
if i == 0 { return None; }
return Some(i);
}
return None;
}
// good — scan finds candidate, validate checks it
fn scan(input: &str) -> Option<Boundary> { ... } // walks bytes
fn validate(pos: usize, bytes: &[u8]) -> Option<Boundary> { ... } // checks invariants
```
### Iterator chains over indexed loops
Prefer `.iter()`, `.map()`, `.filter()`, `.collect()` over `for i in 0..len` with manual indexing. Iterator chains are bounds-checked by construction.
### `Cow<'a, str>` for conditional ownership
GitHubで見る