red64-cli 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -58
- package/dist/components/screens/StartScreen.d.ts.map +1 -1
- package/dist/components/screens/StartScreen.js +2 -2
- package/dist/components/screens/StartScreen.js.map +1 -1
- package/dist/services/AgentInvoker.js +4 -4
- package/dist/services/AgentInvoker.js.map +1 -1
- package/dist/services/ClaudeHealthCheck.d.ts +5 -0
- package/dist/services/ClaudeHealthCheck.d.ts.map +1 -1
- package/dist/services/ClaudeHealthCheck.js +43 -5
- package/dist/services/ClaudeHealthCheck.js.map +1 -1
- package/dist/services/index.d.ts +1 -1
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +1 -1
- package/dist/services/index.js.map +1 -1
- package/framework/stacks/c/code-quality.md +326 -0
- package/framework/stacks/c/coding-style.md +347 -0
- package/framework/stacks/c/conventions.md +513 -0
- package/framework/stacks/c/error-handling.md +350 -0
- package/framework/stacks/c/feedback.md +158 -0
- package/framework/stacks/c/memory-safety.md +408 -0
- package/framework/stacks/c/tech.md +122 -0
- package/framework/stacks/c/testing.md +472 -0
- package/framework/stacks/cpp/code-quality.md +282 -0
- package/framework/stacks/cpp/coding-style.md +363 -0
- package/framework/stacks/cpp/conventions.md +420 -0
- package/framework/stacks/cpp/error-handling.md +264 -0
- package/framework/stacks/cpp/feedback.md +104 -0
- package/framework/stacks/cpp/memory-safety.md +351 -0
- package/framework/stacks/cpp/tech.md +160 -0
- package/framework/stacks/cpp/testing.md +323 -0
- package/framework/stacks/java/code-quality.md +357 -0
- package/framework/stacks/java/coding-style.md +400 -0
- package/framework/stacks/java/conventions.md +437 -0
- package/framework/stacks/java/error-handling.md +408 -0
- package/framework/stacks/java/feedback.md +180 -0
- package/framework/stacks/java/tech.md +126 -0
- package/framework/stacks/java/testing.md +485 -0
- package/framework/stacks/javascript/async-patterns.md +216 -0
- package/framework/stacks/javascript/code-quality.md +182 -0
- package/framework/stacks/javascript/coding-style.md +293 -0
- package/framework/stacks/javascript/conventions.md +268 -0
- package/framework/stacks/javascript/error-handling.md +216 -0
- package/framework/stacks/javascript/feedback.md +80 -0
- package/framework/stacks/javascript/tech.md +114 -0
- package/framework/stacks/javascript/testing.md +209 -0
- package/framework/stacks/loco/code-quality.md +156 -0
- package/framework/stacks/loco/coding-style.md +247 -0
- package/framework/stacks/loco/error-handling.md +225 -0
- package/framework/stacks/loco/feedback.md +35 -0
- package/framework/stacks/loco/loco.md +342 -0
- package/framework/stacks/loco/structure.md +193 -0
- package/framework/stacks/loco/tech.md +129 -0
- package/framework/stacks/loco/testing.md +211 -0
- package/framework/stacks/rust/code-quality.md +370 -0
- package/framework/stacks/rust/coding-style.md +475 -0
- package/framework/stacks/rust/conventions.md +430 -0
- package/framework/stacks/rust/error-handling.md +399 -0
- package/framework/stacks/rust/feedback.md +152 -0
- package/framework/stacks/rust/memory-safety.md +398 -0
- package/framework/stacks/rust/tech.md +121 -0
- package/framework/stacks/rust/testing.md +528 -0
- package/package.json +14 -2
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
# Memory Safety Patterns
|
|
2
|
+
|
|
3
|
+
Ownership, borrowing, lifetimes, and safe concurrency patterns for Rust applications. Guidelines for when and how to use `unsafe`.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Philosophy
|
|
8
|
+
|
|
9
|
+
- **Ownership is the foundation**: Understand move semantics before reaching for `Clone`
|
|
10
|
+
- **Borrow, do not copy**: Pass references by default; clone only when necessary
|
|
11
|
+
- **Minimize unsafe**: Every `unsafe` block is a contract you must uphold manually
|
|
12
|
+
- **Concurrency through types**: `Arc`, `Mutex`, and channels prevent data races at compile time
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Ownership and Borrowing
|
|
17
|
+
|
|
18
|
+
### The Three Rules
|
|
19
|
+
|
|
20
|
+
1. Each value has exactly one owner
|
|
21
|
+
2. When the owner goes out of scope, the value is dropped
|
|
22
|
+
3. You can have either one mutable reference OR any number of immutable references
|
|
23
|
+
|
|
24
|
+
```rust
|
|
25
|
+
// GOOD: Borrowing -- no allocation, no move
|
|
26
|
+
fn process_name(name: &str) -> String {
|
|
27
|
+
name.trim().to_uppercase()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let name = String::from(" Alice ");
|
|
31
|
+
let processed = process_name(&name); // Borrows name
|
|
32
|
+
println!("{name}"); // name is still valid
|
|
33
|
+
|
|
34
|
+
// GOOD: Moving when ownership transfer is intentional
|
|
35
|
+
fn consume_request(req: Request) -> Response {
|
|
36
|
+
// req is owned here, dropped at end of function
|
|
37
|
+
Response::from(req)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// BAD: Unnecessary clone to satisfy borrow checker
|
|
41
|
+
fn process_name_bad(name: String) -> String {
|
|
42
|
+
name.trim().to_uppercase()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let name = String::from(" Alice ");
|
|
46
|
+
let processed = process_name_bad(name.clone()); // Wasteful clone
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Clone vs Borrow Decision
|
|
50
|
+
|
|
51
|
+
| Situation | Use | Reason |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| Reading data | `&T` | Zero cost, no allocation |
|
|
54
|
+
| Modifying data | `&mut T` | Exclusive access, no allocation |
|
|
55
|
+
| Transferring ownership | `T` (move) | Clear ownership transfer |
|
|
56
|
+
| Shared ownership needed | `Arc<T>` | Multiple owners, reference counted |
|
|
57
|
+
| Small Copy types (`i32`, `bool`) | Copy | Trivially cheap |
|
|
58
|
+
| Data needed beyond borrow scope | `Clone` | Only when lifetime cannot work |
|
|
59
|
+
|
|
60
|
+
```rust
|
|
61
|
+
// GOOD: Borrow for read access
|
|
62
|
+
fn total_price(items: &[OrderItem]) -> Decimal {
|
|
63
|
+
items.iter().map(|i| i.price).sum()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// GOOD: Clone when data must outlive the borrow
|
|
67
|
+
fn spawn_background_task(config: &Config) {
|
|
68
|
+
let config = config.clone(); // Clone needed: task outlives the borrow
|
|
69
|
+
tokio::spawn(async move {
|
|
70
|
+
process_with_config(&config).await;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// BAD: Clone when a reference would work
|
|
75
|
+
fn total_price_bad(items: Vec<OrderItem>) -> Decimal {
|
|
76
|
+
items.iter().map(|i| i.price).sum()
|
|
77
|
+
// items is moved in and dropped -- caller loses access
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Lifetime Annotations
|
|
84
|
+
|
|
85
|
+
### When You Need Them
|
|
86
|
+
|
|
87
|
+
Lifetimes are needed when the compiler cannot infer the relationship between input and output references:
|
|
88
|
+
|
|
89
|
+
```rust
|
|
90
|
+
// GOOD: Lifetime ties output reference to input
|
|
91
|
+
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
|
|
92
|
+
if a.len() >= b.len() { a } else { b }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// GOOD: Struct with borrowed data
|
|
96
|
+
struct Request<'a> {
|
|
97
|
+
path: &'a str,
|
|
98
|
+
headers: &'a [(String, String)],
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// GOOD: Lifetime elision -- compiler infers this
|
|
102
|
+
fn first_word(s: &str) -> &str {
|
|
103
|
+
s.split_whitespace().next().unwrap_or("")
|
|
104
|
+
}
|
|
105
|
+
// Equivalent to: fn first_word<'a>(s: &'a str) -> &'a str
|
|
106
|
+
|
|
107
|
+
// BAD: Storing references when you should own
|
|
108
|
+
struct UserCache {
|
|
109
|
+
users: Vec<&str>, // Dangling reference risk
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// GOOD: Own the data in long-lived structs
|
|
113
|
+
struct UserCache {
|
|
114
|
+
users: Vec<String>,
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Lifetime Elision Rules
|
|
119
|
+
|
|
120
|
+
The compiler automatically infers lifetimes when:
|
|
121
|
+
|
|
122
|
+
1. Each reference parameter gets its own lifetime
|
|
123
|
+
2. If there is exactly one input lifetime, it is assigned to all output lifetimes
|
|
124
|
+
3. If there is a `&self` or `&mut self`, its lifetime is assigned to all output lifetimes
|
|
125
|
+
|
|
126
|
+
```rust
|
|
127
|
+
// All of these are equivalent (elision applies):
|
|
128
|
+
fn trim(s: &str) -> &str { ... }
|
|
129
|
+
fn trim<'a>(s: &'a str) -> &'a str { ... }
|
|
130
|
+
|
|
131
|
+
// Elision does NOT apply here (multiple inputs, no self):
|
|
132
|
+
// Must annotate explicitly
|
|
133
|
+
fn pick<'a>(a: &'a str, b: &str) -> &'a str { ... }
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Shared Ownership: `Arc<Mutex<T>>`
|
|
139
|
+
|
|
140
|
+
### Sharing State Across Async Tasks
|
|
141
|
+
|
|
142
|
+
```rust
|
|
143
|
+
use std::sync::Arc;
|
|
144
|
+
use tokio::sync::Mutex;
|
|
145
|
+
|
|
146
|
+
// GOOD: Arc<Mutex<T>> for shared mutable state
|
|
147
|
+
#[derive(Clone)]
|
|
148
|
+
struct AppState {
|
|
149
|
+
db: PgPool,
|
|
150
|
+
cache: Arc<Mutex<HashMap<String, CachedValue>>>,
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async fn get_cached_or_fetch(state: &AppState, key: &str) -> Result<String> {
|
|
154
|
+
// Check cache first
|
|
155
|
+
{
|
|
156
|
+
let cache = state.cache.lock().await;
|
|
157
|
+
if let Some(value) = cache.get(key) {
|
|
158
|
+
return Ok(value.data.clone());
|
|
159
|
+
}
|
|
160
|
+
} // Lock is dropped here
|
|
161
|
+
|
|
162
|
+
// Fetch and cache
|
|
163
|
+
let value = fetch_from_db(&state.db, key).await?;
|
|
164
|
+
{
|
|
165
|
+
let mut cache = state.cache.lock().await;
|
|
166
|
+
cache.insert(key.to_string(), CachedValue { data: value.clone() });
|
|
167
|
+
}
|
|
168
|
+
Ok(value)
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Choosing the Right Synchronization Primitive
|
|
173
|
+
|
|
174
|
+
| Primitive | Use Case |
|
|
175
|
+
|---|---|
|
|
176
|
+
| `Arc<T>` | Shared immutable data across threads |
|
|
177
|
+
| `Arc<Mutex<T>>` | Shared mutable data, low contention |
|
|
178
|
+
| `Arc<RwLock<T>>` | Shared data, many readers, few writers |
|
|
179
|
+
| `tokio::sync::Mutex` | Holding lock across `.await` points |
|
|
180
|
+
| `std::sync::Mutex` | Short critical sections, no `.await` inside |
|
|
181
|
+
| `tokio::sync::mpsc` | Message passing between tasks |
|
|
182
|
+
| `tokio::sync::watch` | Single-producer, multi-consumer broadcast |
|
|
183
|
+
| `dashmap::DashMap` | Concurrent hashmap, high contention |
|
|
184
|
+
|
|
185
|
+
```rust
|
|
186
|
+
// GOOD: Use tokio::sync::Mutex when holding across .await
|
|
187
|
+
let data = state.cache.lock().await; // tokio Mutex
|
|
188
|
+
let result = fetch_something(&data).await; // .await while holding lock is OK
|
|
189
|
+
|
|
190
|
+
// BAD: std::sync::Mutex across .await points
|
|
191
|
+
let data = state.cache.lock().unwrap(); // std Mutex
|
|
192
|
+
let result = fetch_something(&data).await; // BLOCKS the runtime thread!
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Interior Mutability
|
|
198
|
+
|
|
199
|
+
### `Cell` and `RefCell`
|
|
200
|
+
|
|
201
|
+
```rust
|
|
202
|
+
use std::cell::{Cell, RefCell};
|
|
203
|
+
|
|
204
|
+
// Cell: for Copy types, zero overhead
|
|
205
|
+
struct Counter {
|
|
206
|
+
count: Cell<u32>,
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
impl Counter {
|
|
210
|
+
fn increment(&self) { // Note: &self, not &mut self
|
|
211
|
+
self.count.set(self.count.get() + 1);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// RefCell: for non-Copy types, runtime borrow checking
|
|
216
|
+
struct Logger {
|
|
217
|
+
messages: RefCell<Vec<String>>,
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
impl Logger {
|
|
221
|
+
fn log(&self, msg: String) {
|
|
222
|
+
self.messages.borrow_mut().push(msg);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
fn dump(&self) -> Vec<String> {
|
|
226
|
+
self.messages.borrow().clone()
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// WARNING: RefCell panics on double mutable borrow at runtime
|
|
231
|
+
// BAD: This will panic
|
|
232
|
+
fn bad_refcell() {
|
|
233
|
+
let data = RefCell::new(vec![1, 2, 3]);
|
|
234
|
+
let mut a = data.borrow_mut();
|
|
235
|
+
let mut b = data.borrow_mut(); // PANIC: already borrowed mutably
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### When to Use Which
|
|
240
|
+
|
|
241
|
+
| Type | Thread-safe? | Overhead | Use Case |
|
|
242
|
+
|---|---|---|---|
|
|
243
|
+
| `Cell<T>` | No | Zero | Single-thread, Copy types |
|
|
244
|
+
| `RefCell<T>` | No | Runtime check | Single-thread, non-Copy |
|
|
245
|
+
| `Mutex<T>` | Yes | Lock | Multi-thread |
|
|
246
|
+
| `RwLock<T>` | Yes | Lock | Multi-thread, read-heavy |
|
|
247
|
+
| `AtomicU64` etc. | Yes | Atomic op | Counters, flags |
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## Pin and Self-Referential Types
|
|
252
|
+
|
|
253
|
+
### When Pin Is Needed
|
|
254
|
+
|
|
255
|
+
Pin is required for self-referential types (most commonly, async futures):
|
|
256
|
+
|
|
257
|
+
```rust
|
|
258
|
+
use std::pin::Pin;
|
|
259
|
+
use std::future::Future;
|
|
260
|
+
|
|
261
|
+
// GOOD: Returning pinned futures (common in trait methods)
|
|
262
|
+
trait Service {
|
|
263
|
+
fn call(&self, req: Request) -> Pin<Box<dyn Future<Output = Response> + Send + '_>>;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
impl Service for MyService {
|
|
267
|
+
fn call(&self, req: Request) -> Pin<Box<dyn Future<Output = Response> + Send + '_>> {
|
|
268
|
+
Box::pin(async move {
|
|
269
|
+
// async body
|
|
270
|
+
Response::ok()
|
|
271
|
+
})
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// In most application code, you do NOT need Pin directly.
|
|
276
|
+
// async/await handles it automatically.
|
|
277
|
+
async fn handle_request(req: Request) -> Response {
|
|
278
|
+
// The compiler pins the future for you
|
|
279
|
+
Response::ok()
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## Unsafe Guidelines
|
|
286
|
+
|
|
287
|
+
### Rules for `unsafe` Code
|
|
288
|
+
|
|
289
|
+
1. **Minimize scope**: Keep `unsafe` blocks as small as possible
|
|
290
|
+
2. **Document invariants**: Explain why the unsafe operation is sound
|
|
291
|
+
3. **Test with Miri**: Run `cargo +nightly miri test` on all unsafe code
|
|
292
|
+
4. **Encapsulate**: Wrap unsafe code in a safe API
|
|
293
|
+
|
|
294
|
+
```rust
|
|
295
|
+
// GOOD: Minimal unsafe scope with documented invariants
|
|
296
|
+
/// Returns a reference to the element at `index` without bounds checking.
|
|
297
|
+
///
|
|
298
|
+
/// # Safety
|
|
299
|
+
///
|
|
300
|
+
/// The caller must ensure that `index < self.len()`.
|
|
301
|
+
pub unsafe fn get_unchecked(&self, index: usize) -> &T {
|
|
302
|
+
// SAFETY: Caller guarantees index is in bounds.
|
|
303
|
+
unsafe { self.data.get_unchecked(index) }
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// GOOD: Safe wrapper around unsafe
|
|
307
|
+
pub fn get(&self, index: usize) -> Option<&T> {
|
|
308
|
+
if index < self.len() {
|
|
309
|
+
// SAFETY: We just checked that index < len.
|
|
310
|
+
Some(unsafe { self.get_unchecked(index) })
|
|
311
|
+
} else {
|
|
312
|
+
None
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// BAD: Large unsafe block with no documentation
|
|
317
|
+
unsafe {
|
|
318
|
+
let ptr = data.as_ptr();
|
|
319
|
+
let len = data.len();
|
|
320
|
+
let slice = std::slice::from_raw_parts(ptr, len);
|
|
321
|
+
let result = process(slice);
|
|
322
|
+
std::ptr::copy_nonoverlapping(result.as_ptr(), output.as_mut_ptr(), result.len());
|
|
323
|
+
output.set_len(result.len());
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### When Unsafe Is Justified
|
|
328
|
+
|
|
329
|
+
| Use Case | Justification |
|
|
330
|
+
|---|---|
|
|
331
|
+
| FFI (calling C code) | No safe alternative |
|
|
332
|
+
| Performance-critical inner loops | Measured, benchmarked, significant gain |
|
|
333
|
+
| Implementing data structures | Self-referential or intrusive structures |
|
|
334
|
+
| Hardware/OS interaction | Low-level system programming |
|
|
335
|
+
|
|
336
|
+
### When Unsafe Is NOT Justified
|
|
337
|
+
|
|
338
|
+
| Use Case | Safe Alternative |
|
|
339
|
+
|---|---|
|
|
340
|
+
| "The borrow checker is annoying" | Refactor ownership, use `Arc`/`Rc` |
|
|
341
|
+
| Skip bounds checking "for speed" | Profile first; usually negligible |
|
|
342
|
+
| Transmute between types | Use `From`/`Into` or `bytemuck` |
|
|
343
|
+
| Global mutable state | Use `once_cell::sync::Lazy` or `std::sync::OnceLock` |
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
## Common Ownership Patterns
|
|
348
|
+
|
|
349
|
+
### Axum State Sharing
|
|
350
|
+
|
|
351
|
+
```rust
|
|
352
|
+
// GOOD: Shared application state with Axum
|
|
353
|
+
#[derive(Clone)]
|
|
354
|
+
struct AppState {
|
|
355
|
+
db: PgPool, // PgPool is already Arc internally
|
|
356
|
+
config: Arc<Config>, // Immutable shared config
|
|
357
|
+
rate_limiter: Arc<Mutex<RateLimiter>>, // Mutable shared state
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
async fn create_app() -> Router {
|
|
361
|
+
let state = AppState {
|
|
362
|
+
db: PgPool::connect(&db_url).await.unwrap(),
|
|
363
|
+
config: Arc::new(Config::from_env()),
|
|
364
|
+
rate_limiter: Arc::new(Mutex::new(RateLimiter::new())),
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
Router::new()
|
|
368
|
+
.route("/users", post(create_user))
|
|
369
|
+
.with_state(state)
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async fn create_user(
|
|
373
|
+
State(state): State<AppState>,
|
|
374
|
+
Json(data): Json<CreateUserRequest>,
|
|
375
|
+
) -> Result<Json<UserResponse>, AppError> {
|
|
376
|
+
// state.db is cheaply cloned (Arc internally)
|
|
377
|
+
let user = insert_user(&state.db, &data).await?;
|
|
378
|
+
Ok(Json(UserResponse::from(user)))
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
## Anti-Patterns
|
|
385
|
+
|
|
386
|
+
| Anti-Pattern | Problem | Correct Approach |
|
|
387
|
+
|---|---|---|
|
|
388
|
+
| `clone()` to silence borrow checker | Hidden allocations, hides design issues | Refactor to proper ownership or use `Arc` |
|
|
389
|
+
| `Rc` in async code | Not `Send`, cannot cross `.await` | Use `Arc` in async contexts |
|
|
390
|
+
| `std::sync::Mutex` across `.await` | Blocks runtime thread | Use `tokio::sync::Mutex` |
|
|
391
|
+
| Large `unsafe` blocks | Hard to audit, likely unsound | Minimize scope, document each operation |
|
|
392
|
+
| `'static` lifetime everywhere | Prevents borrowing, forces allocation | Use proper lifetime parameters |
|
|
393
|
+
| `RefCell` in multi-threaded code | Not thread-safe, compiles but panics | Use `Mutex` or `RwLock` |
|
|
394
|
+
| Leaking memory with `Box::leak` | Never reclaimed | Use `Arc` or `OnceLock` for global state |
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
_Rust's ownership model is not a constraint to work around -- it is a design tool. Lean into it, and your programs will be correct by construction._
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Technology Stack
|
|
2
|
+
|
|
3
|
+
## Architecture
|
|
4
|
+
|
|
5
|
+
Modern Rust application with async-first design. Axum or Actix-web as web framework, PostgreSQL for persistence, Redis for caching, Tokio as async runtime, Docker for deployment.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Core Technologies
|
|
10
|
+
|
|
11
|
+
- **Language**: Rust stable (Edition 2024, 1.85+)
|
|
12
|
+
- **Build System**: Cargo (with cargo-nextest for testing)
|
|
13
|
+
- **Web Framework**: Axum (default) or Actix-web (high-throughput)
|
|
14
|
+
- **Async Runtime**: Tokio (multi-threaded)
|
|
15
|
+
- **Database**: PostgreSQL with SQLx (async, compile-time checked)
|
|
16
|
+
- **Serialization**: serde + serde_json
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Key Libraries
|
|
21
|
+
|
|
22
|
+
### Web & API
|
|
23
|
+
- **Axum**: Tower-based web framework, excellent DX, Tokio ecosystem native
|
|
24
|
+
- **Actix-web**: Actor-based, highest raw throughput for extreme concurrency
|
|
25
|
+
- **Tower**: Middleware and service abstractions (used by Axum)
|
|
26
|
+
- **tower-http**: HTTP-specific middleware (CORS, compression, tracing)
|
|
27
|
+
|
|
28
|
+
### Database & Storage
|
|
29
|
+
- **SQLx**: Async, compile-time verified SQL queries (PostgreSQL, MySQL, SQLite)
|
|
30
|
+
- **Diesel**: Sync ORM with strong type safety and migration system
|
|
31
|
+
- **SeaORM**: Async ORM built on SQLx, ActiveRecord-style
|
|
32
|
+
- **deadpool**: Connection pooling for async database drivers
|
|
33
|
+
|
|
34
|
+
### Serialization & Validation
|
|
35
|
+
- **serde**: Derive-based serialization/deserialization framework
|
|
36
|
+
- **serde_json**: JSON support
|
|
37
|
+
- **validator**: Struct validation with derive macros
|
|
38
|
+
- **utoipa**: OpenAPI documentation generation from code
|
|
39
|
+
|
|
40
|
+
### Error Handling
|
|
41
|
+
- **thiserror**: Derive macro for custom error types (libraries)
|
|
42
|
+
- **anyhow**: Ergonomic error handling for applications
|
|
43
|
+
- **miette**: Diagnostic error reporting with source spans
|
|
44
|
+
|
|
45
|
+
### CLI
|
|
46
|
+
- **clap**: Command-line argument parser with derive macros
|
|
47
|
+
- **dialoguer**: Interactive prompts
|
|
48
|
+
- **indicatif**: Progress bars and spinners
|
|
49
|
+
|
|
50
|
+
### Observability
|
|
51
|
+
- **tracing**: Structured, async-aware logging and diagnostics
|
|
52
|
+
- **tracing-subscriber**: Log formatting and filtering
|
|
53
|
+
- **opentelemetry**: Distributed tracing integration
|
|
54
|
+
|
|
55
|
+
### Testing
|
|
56
|
+
- **cargo test / cargo-nextest**: Test execution
|
|
57
|
+
- **mockall**: Trait-based mocking framework
|
|
58
|
+
- **proptest**: Property-based testing
|
|
59
|
+
- **wiremock**: HTTP mock server for integration tests
|
|
60
|
+
- **rstest**: Fixture and parametrize support
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Development Environment
|
|
65
|
+
|
|
66
|
+
### Required Tools
|
|
67
|
+
- Rust stable 1.85+ (Edition 2024)
|
|
68
|
+
- Cargo (bundled with rustup)
|
|
69
|
+
- PostgreSQL 16+
|
|
70
|
+
- Redis 7+
|
|
71
|
+
- Docker & Docker Compose
|
|
72
|
+
|
|
73
|
+
### Common Commands
|
|
74
|
+
```bash
|
|
75
|
+
# Environment setup
|
|
76
|
+
rustup update stable # Update toolchain
|
|
77
|
+
cargo build # Build project
|
|
78
|
+
cargo build --release # Release build
|
|
79
|
+
|
|
80
|
+
# Dev server (Axum example)
|
|
81
|
+
cargo run # Run application
|
|
82
|
+
cargo watch -x run # Auto-reload on changes
|
|
83
|
+
|
|
84
|
+
# Tests
|
|
85
|
+
cargo test # All tests
|
|
86
|
+
cargo nextest run # Faster parallel test runner
|
|
87
|
+
cargo test --doc # Doctests only
|
|
88
|
+
|
|
89
|
+
# Code quality
|
|
90
|
+
cargo clippy -- -D warnings # Lint (deny warnings)
|
|
91
|
+
cargo fmt # Format
|
|
92
|
+
cargo fmt -- --check # Verify formatting
|
|
93
|
+
|
|
94
|
+
# Database (SQLx)
|
|
95
|
+
sqlx migrate run # Apply migrations
|
|
96
|
+
sqlx migrate add <name> # Create new migration
|
|
97
|
+
sqlx prepare # Generate offline query data
|
|
98
|
+
|
|
99
|
+
# Docker
|
|
100
|
+
docker compose up -d # Start services
|
|
101
|
+
docker compose logs -f app # Follow app logs
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Key Technical Decisions
|
|
107
|
+
|
|
108
|
+
| Decision | Rationale |
|
|
109
|
+
|----------|-----------|
|
|
110
|
+
| **Axum over Actix-web** | Better DX, Tokio-native, Tower middleware ecosystem, most adopted since 2023 |
|
|
111
|
+
| **SQLx over Diesel** | Async-native, compile-time SQL verification, no DSL learning curve |
|
|
112
|
+
| **Tokio runtime** | Industry standard, largest async ecosystem, multi-threaded by default |
|
|
113
|
+
| **thiserror + anyhow** | thiserror for library error types, anyhow for application-level propagation |
|
|
114
|
+
| **tracing over log** | Structured, span-based, async-aware, OpenTelemetry compatible |
|
|
115
|
+
| **serde for serialization** | Universal Rust standard, zero-cost abstractions, derive macros |
|
|
116
|
+
| **cargo-nextest** | Faster parallel execution, better output, retries support |
|
|
117
|
+
| **Edition 2024** | Latest stable edition with improved ergonomics and safety defaults |
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
_Document standards and patterns, not every dependency. See individual steering docs for detailed conventions._
|