idioms.md

  1# Rust Idioms and Design Patterns
  2
  3## Contents
  4
  5- [Idioms](#idioms)
  6  - [Borrowed types for arguments](#borrowed-types-for-arguments)
  7  - [String concatenation with format!](#string-concatenation-with-format)
  8  - [Constructors and Default](#constructors-and-default)
  9  - [Collections as smart pointers](#collections-as-smart-pointers)
 10  - [Finalisation in destructors](#finalisation-in-destructors)
 11  - [mem::take and mem::replace](#memtake-and-memreplace)
 12  - [On-stack dynamic dispatch](#on-stack-dynamic-dispatch)
 13  - [Iterating over Option](#iterating-over-option)
 14  - [Pass variables to closure](#pass-variables-to-closure)
 15  - [non_exhaustive for extensibility](#non_exhaustive-for-extensibility)
 16  - [Temporary mutability](#temporary-mutability)
 17  - [Return consumed argument on error](#return-consumed-argument-on-error)
 18- [Behavioural patterns](#behavioural-patterns)
 19  - [Command](#command)
 20  - [Interpreter](#interpreter)
 21  - [Newtype](#newtype)
 22  - [RAII with guards](#raii-with-guards)
 23  - [Strategy](#strategy)
 24  - [Visitor](#visitor)
 25- [Creational patterns](#creational-patterns)
 26  - [Builder](#builder)
 27  - [Fold](#fold)
 28- [Structural patterns](#structural-patterns)
 29  - [Struct decomposition for independent borrowing](#struct-decomposition-for-independent-borrowing)
 30  - [Prefer small crates](#prefer-small-crates)
 31  - [Contain unsafety in small modules](#contain-unsafety-in-small-modules)
 32  - [Custom traits for complex type bounds](#custom-traits-for-complex-type-bounds)
 33- [Functional patterns](#functional-patterns)
 34
 35## Idioms
 36
 37### Borrowed types for arguments
 38
 39Prefer `&str` over `&String`, `&[T]` over `&Vec<T>`, and `&T` over `&Box<T>` in function parameters. This avoids unnecessary indirection and accepts more input types through deref coercion.
 40
 41### String concatenation with format!
 42
 43Prefer `format!("Hello {name}!")` over manual `push`/`push_str` chains for readability. For performance-critical paths where the string can be pre-allocated, manual push operations may be faster.
 44
 45### Constructors and Default
 46
 47Rust has no language-level constructors. Use an associated function called `new` to create objects. If the type has a sensible zero/empty state, implement the `Default` trait. It is common and expected to provide both `Default` and `new`, even if they are functionally identical.
 48
 49`Default` enables usage with `or_default` functions throughout the standard library and partial initialization: `MyStruct { field: value, ..Default::default() }`.
 50
 51### Collections as smart pointers
 52
 53Implement `Deref` for owning collections to provide a borrowed view (e.g., `Vec<T>``&[T]`, `String``&str`). Implement methods on the borrowed view rather than the owning type where possible.
 54
 55### Finalisation in destructors
 56
 57Use `Drop` implementations to ensure cleanup code runs on all exit paths (early returns, `?`, panics). Assign the guard object to a named variable (not just `_`) to prevent immediate destruction. Destructors are not guaranteed to run in all cases (infinite loops, double panics).
 58
 59### mem::take and mem::replace
 60
 61When transforming an enum variant in place, use `mem::take(name)` to move values out without cloning. This avoids the "clone to satisfy the borrow checker" anti-pattern. For `Option` fields, prefer `Option::take()`.
 62
 63### On-stack dynamic dispatch
 64
 65When dynamic dispatch is needed but heap allocation is not, use `&mut dyn Trait` with temporary values. Since Rust 1.79.0, the compiler automatically extends lifetimes of temporaries in `&` or `&mut`.
 66
 67### Iterating over Option
 68
 69`Option` implements `IntoIterator`, so it works with `.extend()`, `.chain()`, and `for` loops. Use `std::iter::once` as a more readable alternative to `Some(foo).into_iter()` when the value is always present.
 70
 71### Pass variables to closure
 72
 73Use a separate scope block before the closure to prepare variables (clone, borrow, move) rather than creating separate named variables like `num2_cloned`. This groups captured state with the closure's definition.
 74
 75### non_exhaustive for extensibility
 76
 77Apply `#[non_exhaustive]` to public structs and enums that may gain fields or variants in the future, to maintain backwards compatibility across crate boundaries. Within a crate, a private field (e.g., `_b: ()`) achieves a similar effect. Use deliberately — incrementing the major version when adding fields or variants is often better.
 78
 79### Temporary mutability
 80
 81When data must be prepared mutably but then used immutably, use a nested block or variable rebinding (`let data = data;`) to enforce immutability after preparation.
 82
 83### Return consumed argument on error
 84
 85If a fallible function takes ownership of an argument, include that argument in the error type so the caller can recover it and retry. Example: `String::from_utf8` returns the original `Vec<u8>` inside `FromUtf8Error`.
 86
 87## Behavioural patterns
 88
 89### Command
 90
 91Separate actions into objects and pass them as parameters. Three approaches: trait objects (complex commands with state), function pointers (simple stateless commands), and `Fn` trait objects (closures). Use trait objects when commands need multiple functions and state; use function pointers or closures for simple, stateless cases.
 92
 93### Interpreter
 94
 95Express recurring problem instances in a domain-specific language and implement an interpreter. `macro_rules!` can serve as a lightweight compile-time interpreter for simple DSLs.
 96
 97### Newtype
 98
 99Use a tuple struct with a single field to create a distinct type (e.g., `struct Password(String)`). Provides type safety, encapsulation, and the ability to implement custom traits on existing types. Zero-cost abstraction. Consider the `derive_more` crate to reduce boilerplate pass-through impls.
100
101### RAII with guards
102
103Tie resource acquisition to object creation and release to destruction (`Drop`). Use guard objects to mediate access — the borrow checker ensures references cannot outlive the guard. Classic example: `MutexGuard`.
104
105### Strategy
106
107Define an abstract algorithm skeleton and let implementations be swapped via traits or closures. Serde is an excellent real-world example: `Serialize`/`Deserialize` traits allow swapping formats transparently.
108
109### Visitor
110
111Encapsulate an algorithm operating over a heterogeneous collection without modifying the data types. Define `visit_*` methods on a `Visitor` trait for each type; provide `walk_*` helpers to factor out traversal. The visitor can be stateful.
112
113## Creational patterns
114
115### Builder
116
117Construct complex objects step by step using a separate builder type. Provide a `builder()` method on the target type. Return the builder by value from each setter for method chaining: `FooBuilder::new().name("x").build()`. Consider the `derive_builder` crate.
118
119### Fold
120
121Transform a data structure by running an algorithm over each node, producing a new structure. Provide default `fold_*` methods that recurse into children, allowing implementors to override only the nodes they care about. Related to visitor, but produces a new structure.
122
123## Structural patterns
124
125### Struct decomposition for independent borrowing
126
127When the borrow checker prevents simultaneous borrows of different fields in a large struct, decompose into smaller structs. Compose them back; each can then be borrowed independently. Often leads to better design.
128
129### Prefer small crates
130
131Build small, focused crates that do one thing well. Easier to understand, encourage modularity, and allow parallel compilation. Be mindful of dependency quality.
132
133### Contain unsafety in small modules
134
135Isolate `unsafe` code in the smallest possible module that upholds the needed invariants. Build a safe interface on top. This restricts the audit surface.
136
137### Custom traits for complex type bounds
138
139When trait bounds become unwieldy (especially with `Fn` traits), introduce a new trait with a generic `impl` for all types satisfying the original bound. Reduces verbosity and increases expressiveness.
140
141## Functional patterns
142
143Rust supports many functional paradigms alongside its imperative core:
144
145- Prefer declarative iterator chains (`.fold()`, `.map()`, `.filter()`) over imperative loops when they improve clarity.
146- Use generics as type classes. Rust's generic type parameters create type class constraints; different filled-in parameters create different types with potentially different `impl` blocks.
147- Apply YAGNI — many traditional OO patterns are unnecessary in Rust due to traits, enums, and the type system.