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.