agentic-team-templates 0.10.0 → 0.12.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.
@@ -0,0 +1,190 @@
1
+ # Rust Error Handling
2
+
3
+ Rust has no exceptions. `Result<T, E>` and `Option<T>` are the error handling primitives. The `?` operator propagates errors ergonomically, but every error path is still an explicit decision.
4
+
5
+ ## Custom Error Types
6
+
7
+ ### Using thiserror (Libraries)
8
+
9
+ ```rust
10
+ use thiserror::Error;
11
+
12
+ #[derive(Debug, Error)]
13
+ pub enum AppError {
14
+ #[error("configuration error: {message}")]
15
+ Config { message: String },
16
+
17
+ #[error("database query failed")]
18
+ Database(#[from] sqlx::Error),
19
+
20
+ #[error("request to {url} failed")]
21
+ Http {
22
+ url: String,
23
+ #[source]
24
+ source: reqwest::Error,
25
+ },
26
+
27
+ #[error("record not found: {entity} with id {id}")]
28
+ NotFound { entity: &'static str, id: String },
29
+
30
+ #[error("validation failed: {0}")]
31
+ Validation(String),
32
+ }
33
+ ```
34
+
35
+ ### Using anyhow (Applications)
36
+
37
+ ```rust
38
+ use anyhow::{Context, Result, bail, ensure};
39
+
40
+ fn load_config(path: &Path) -> Result<Config> {
41
+ let contents = fs::read_to_string(path)
42
+ .with_context(|| format!("reading config from {}", path.display()))?;
43
+
44
+ let config: Config = toml::from_str(&contents)
45
+ .context("parsing config TOML")?;
46
+
47
+ ensure!(!config.database_url.is_empty(), "database_url must not be empty");
48
+
49
+ if config.port == 0 {
50
+ bail!("port must be non-zero");
51
+ }
52
+
53
+ Ok(config)
54
+ }
55
+ ```
56
+
57
+ ### When to Use Which
58
+
59
+ - **thiserror** — libraries, reusable crates, when callers need to match on error variants
60
+ - **anyhow** — applications, binaries, when you just need to propagate context up to main
61
+ - **Manual impl** — when you need full control or minimal dependencies
62
+
63
+ ### Manual Error Implementation
64
+
65
+ ```rust
66
+ #[derive(Debug)]
67
+ pub enum StorageError {
68
+ Io(std::io::Error),
69
+ Serialization(serde_json::Error),
70
+ NotFound { key: String },
71
+ }
72
+
73
+ impl fmt::Display for StorageError {
74
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75
+ match self {
76
+ Self::Io(e) => write!(f, "storage I/O error: {e}"),
77
+ Self::Serialization(e) => write!(f, "serialization error: {e}"),
78
+ Self::NotFound { key } => write!(f, "key not found: {key}"),
79
+ }
80
+ }
81
+ }
82
+
83
+ impl std::error::Error for StorageError {
84
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
85
+ match self {
86
+ Self::Io(e) => Some(e),
87
+ Self::Serialization(e) => Some(e),
88
+ Self::NotFound { .. } => None,
89
+ }
90
+ }
91
+ }
92
+
93
+ impl From<std::io::Error> for StorageError {
94
+ fn from(e: std::io::Error) -> Self {
95
+ Self::Io(e)
96
+ }
97
+ }
98
+ ```
99
+
100
+ ## The ? Operator
101
+
102
+ ```rust
103
+ // ? is sugar for early return on Err/None
104
+ // It calls From::from() on the error, enabling automatic conversion
105
+
106
+ fn process(path: &Path) -> Result<Data, AppError> {
107
+ let raw = fs::read(path)?; // io::Error -> AppError via From
108
+ let parsed = serde_json::from_slice(&raw)?; // serde::Error -> AppError via From
109
+ Ok(parsed)
110
+ }
111
+
112
+ // ? works with Option too
113
+ fn first_word(s: &str) -> Option<&str> {
114
+ let end = s.find(' ')?; // Returns None if no space
115
+ Some(&s[..end])
116
+ }
117
+ ```
118
+
119
+ ## Option Patterns
120
+
121
+ ```rust
122
+ // Prefer combinators over match when they're clearer
123
+ let name = user.name.as_deref().unwrap_or("anonymous");
124
+
125
+ let parsed = input
126
+ .strip_prefix("v")
127
+ .and_then(|s| s.parse::<u32>().ok());
128
+
129
+ // Use match when you need complex logic per variant
130
+ match config.log_level {
131
+ Some(level) => logger.set_level(level),
132
+ None => logger.set_level(Level::Info),
133
+ }
134
+
135
+ // Use if let for single-variant checks
136
+ if let Some(token) = headers.get("Authorization") {
137
+ authenticate(token)?;
138
+ }
139
+ ```
140
+
141
+ ## Panic Policy
142
+
143
+ ```rust
144
+ // Panics are for bugs, not for expected error conditions.
145
+
146
+ // Acceptable: debug assertions
147
+ debug_assert!(index < len, "index {index} out of bounds for length {len}");
148
+
149
+ // Acceptable: unreachable code paths that truly can't happen
150
+ match state {
151
+ State::Ready => process(),
152
+ State::Done => return,
153
+ // If we've exhaustively handled all variants and the compiler
154
+ // still wants a branch, use unreachable!() — but only when
155
+ // you can prove it's unreachable.
156
+ }
157
+
158
+ // Acceptable: unwrap() in tests
159
+ #[cfg(test)]
160
+ fn setup() -> TestDb {
161
+ TestDb::new().unwrap() // Fine in tests
162
+ }
163
+
164
+ // Never acceptable: unwrap() in library code without a VERY good reason
165
+ // Never acceptable: panic!() for user input validation
166
+ // Never acceptable: todo!() in shipped code
167
+ ```
168
+
169
+ ## Anti-Patterns
170
+
171
+ ```rust
172
+ // Never: Silencing errors
173
+ let _ = write!(f, "data"); // The write might fail — handle it or explain why not
174
+
175
+ // Never: unwrap() with no context
176
+ let config = load_config().unwrap(); // What failed? Where? Why?
177
+ // At minimum:
178
+ let config = load_config().expect("loading config from default path");
179
+
180
+ // Never: Stringly-typed errors
181
+ fn process() -> Result<(), String> { // Don't use String as an error type
182
+ Err("something went wrong".into())
183
+ }
184
+
185
+ // Never: Catching panics as error handling
186
+ let result = std::panic::catch_unwind(|| dangerous_code()); // This is not error handling
187
+
188
+ // Never: .ok() to discard error information without justification
189
+ let maybe = fallible_operation().ok(); // Why is the error not important?
190
+ ```
@@ -0,0 +1,142 @@
1
+ # Rust Expert
2
+
3
+ Guidelines for principal-level Rust engineering. Ownership, zero-cost abstractions, and fearless concurrency — wielded with precision.
4
+
5
+ ## Scope
6
+
7
+ This ruleset applies to:
8
+ - Systems programming and embedded targets
9
+ - Web services and async runtimes (Tokio, async-std)
10
+ - CLI tools and developer utilities
11
+ - Libraries and crates published to crates.io
12
+ - WebAssembly targets
13
+ - FFI and interop with C/C++
14
+ - Performance-critical applications
15
+
16
+ ## Core Philosophy
17
+
18
+ Rust gives you power without sacrificing safety. The compiler is your closest collaborator — work with it, not against it.
19
+
20
+ - **If it compiles, it's probably correct.** The borrow checker isn't an obstacle — it's catching bugs that would have been CVEs in another language. Trust it.
21
+ - **Zero-cost abstractions are the point.** You should never have to choose between expressiveness and performance. If the abstraction has runtime cost, question whether it's the right abstraction.
22
+ - **Make illegal states unrepresentable.** Use the type system to encode invariants. If a state shouldn't exist, make it impossible to construct.
23
+ - **Explicit over implicit.** Lifetimes, ownership, error handling — Rust surfaces what other languages hide. This is a feature, not a flaw.
24
+ - **Unsafe is a scalpel, not a sledgehammer.** Every `unsafe` block is a proof obligation. If you can't explain exactly why it's sound, don't write it.
25
+ - **If you don't know, say so.** The Rust ecosystem is vast and evolving. Admitting uncertainty is better than guessing wrong about soundness.
26
+
27
+ ## Key Principles
28
+
29
+ ### 1. Ownership Is the Architecture
30
+
31
+ Ownership isn't just memory management — it's your design tool:
32
+ - Who owns this data? Who borrows it? For how long?
33
+ - These questions force you to design clear APIs with explicit lifetimes and responsibilities
34
+ - If you're fighting the borrow checker, the design likely needs rethinking — not an `unsafe` escape hatch
35
+
36
+ ### 2. Types Encode Invariants
37
+
38
+ ```rust
39
+ // Make illegal states unrepresentable
40
+ // Bad: bool flags that can be inconsistent
41
+ struct Connection {
42
+ is_connected: bool,
43
+ is_authenticated: bool, // Can this be true if is_connected is false?
44
+ }
45
+
46
+ // Good: State machine as an enum
47
+ enum Connection {
48
+ Disconnected,
49
+ Connected(TcpStream),
50
+ Authenticated { stream: TcpStream, token: AuthToken },
51
+ }
52
+ // It's impossible to be Authenticated without a stream
53
+ ```
54
+
55
+ ### 3. Error Handling Is Explicit
56
+
57
+ ```rust
58
+ // Rust has no exceptions. Result and Option are the tools.
59
+ // The ? operator is syntactic sugar, not an excuse to ignore errors.
60
+
61
+ fn load_config(path: &Path) -> Result<Config, ConfigError> {
62
+ let contents = fs::read_to_string(path)
63
+ .map_err(|e| ConfigError::ReadFailed { path: path.to_owned(), source: e })?;
64
+ let config: Config = toml::from_str(&contents)
65
+ .map_err(|e| ConfigError::ParseFailed { source: e })?;
66
+ config.validate()?;
67
+ Ok(config)
68
+ }
69
+ ```
70
+
71
+ ### 4. Clippy Is Non-Negotiable
72
+
73
+ ```bash
74
+ # In CI, always:
75
+ cargo clippy -- -D warnings
76
+ # Clippy catches real bugs, not just style nits. Treat warnings as errors.
77
+ ```
78
+
79
+ ## Project Structure
80
+
81
+ ```
82
+ my-project/
83
+ ├── Cargo.toml # Workspace or package manifest
84
+ ├── Cargo.lock # Committed for binaries, not for libraries
85
+ ├── src/
86
+ │ ├── main.rs # Binary entry point (or lib.rs for libraries)
87
+ │ ├── lib.rs # Library root — public API surface
88
+ │ ├── config.rs # Configuration loading and validation
89
+ │ ├── error.rs # Error types for this crate
90
+ │ └── domain/ # Core business logic modules
91
+ │ ├── mod.rs
92
+ │ └── ...
93
+ ├── tests/ # Integration tests (separate compilation unit)
94
+ │ └── integration_test.rs
95
+ ├── benches/ # Benchmarks
96
+ │ └── benchmark.rs
97
+ ├── examples/ # Runnable examples
98
+ │ └── basic_usage.rs
99
+ └── build.rs # Build script (if needed)
100
+ ```
101
+
102
+ ### Workspace Layout (Multi-Crate)
103
+
104
+ ```
105
+ workspace/
106
+ ├── Cargo.toml # [workspace] members
107
+ ├── crates/
108
+ │ ├── core/ # Domain logic — no IO, no async
109
+ │ │ ├── Cargo.toml
110
+ │ │ └── src/lib.rs
111
+ │ ├── api/ # HTTP handlers, routes
112
+ │ │ ├── Cargo.toml
113
+ │ │ └── src/lib.rs
114
+ │ ├── db/ # Database access
115
+ │ │ ├── Cargo.toml
116
+ │ │ └── src/lib.rs
117
+ │ └── cli/ # Binary entry point
118
+ │ ├── Cargo.toml
119
+ │ └── src/main.rs
120
+ ```
121
+
122
+ ### Layout Rules
123
+
124
+ - `lib.rs` defines the public API — only `pub` items here are your contract
125
+ - `mod.rs` or file-per-module — be consistent within a project
126
+ - Integration tests in `tests/` are separate compilation units — they test your public API
127
+ - `Cargo.lock` is committed for binaries and applications, not for libraries
128
+ - Feature flags in `Cargo.toml` must be additive — enabling a feature must not break existing code
129
+
130
+ ## Definition of Done
131
+
132
+ A Rust feature is complete when:
133
+ - [ ] `cargo build` compiles with zero warnings
134
+ - [ ] `cargo clippy -- -D warnings` passes
135
+ - [ ] `cargo test` passes (including doc tests)
136
+ - [ ] `cargo fmt -- --check` passes
137
+ - [ ] No `unwrap()` or `expect()` in library code without justification
138
+ - [ ] All `unsafe` blocks have `// SAFETY:` comments explaining the invariants
139
+ - [ ] Error types are meaningful and implement `std::error::Error`
140
+ - [ ] Public API has doc comments with examples
141
+ - [ ] No unnecessary allocations in hot paths
142
+ - [ ] `cargo deny check` passes (license and advisory audit)
@@ -0,0 +1,204 @@
1
+ # Rust Ownership and Borrowing
2
+
3
+ Ownership is Rust's defining feature. It's not a constraint — it's a design tool that eliminates entire classes of bugs at compile time. Every memory safety guarantee flows from these rules.
4
+
5
+ ## The Three Rules
6
+
7
+ 1. Each value has exactly one owner
8
+ 2. When the owner goes out of scope, the value is dropped
9
+ 3. You can have either one mutable reference OR any number of shared references — never both
10
+
11
+ ## Ownership Patterns
12
+
13
+ ### Move Semantics
14
+
15
+ ```rust
16
+ // Values are moved by default — this is the foundation
17
+ let s = String::from("hello");
18
+ let t = s; // s is MOVED into t
19
+ // println!("{s}"); // Compile error: s has been moved
20
+
21
+ // Functions take ownership unless they borrow
22
+ fn consume(s: String) { /* s is dropped at end of function */ }
23
+ fn borrow(s: &str) { /* s is borrowed — caller keeps ownership */ }
24
+
25
+ // Return values transfer ownership back
26
+ fn create() -> String {
27
+ String::from("created") // Ownership moves to caller
28
+ }
29
+ ```
30
+
31
+ ### Borrowing
32
+
33
+ ```rust
34
+ // Shared references: &T — read-only, multiple allowed
35
+ fn len(s: &str) -> usize {
36
+ s.len()
37
+ }
38
+
39
+ // Mutable references: &mut T — exclusive access
40
+ fn push_greeting(s: &mut String) {
41
+ s.push_str(", world!");
42
+ }
43
+
44
+ // The borrow checker enforces at compile time:
45
+ let mut s = String::from("hello");
46
+ let r1 = &s; // Fine: shared borrow
47
+ let r2 = &s; // Fine: multiple shared borrows
48
+ println!("{r1} {r2}");
49
+ // r1 and r2 are no longer used after this point (NLL)
50
+ let r3 = &mut s; // Fine: no shared borrows are active
51
+ r3.push_str("!");
52
+ ```
53
+
54
+ ### Lifetimes
55
+
56
+ ```rust
57
+ // Lifetimes tell the compiler how long references are valid
58
+ // Most of the time, elision handles this automatically
59
+
60
+ // When the compiler needs help:
61
+ fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
62
+ if x.len() > y.len() { x } else { y }
63
+ }
64
+ // Meaning: the returned reference lives at least as long as
65
+ // the shorter of x and y
66
+
67
+ // Structs that hold references need lifetime annotations
68
+ struct Excerpt<'a> {
69
+ text: &'a str,
70
+ }
71
+
72
+ // If your struct has many lifetime parameters, consider
73
+ // owning the data instead — complexity isn't always worth it
74
+ struct Article {
75
+ title: String, // Owned — simpler, no lifetime tracking
76
+ body: String,
77
+ }
78
+ ```
79
+
80
+ ### Interior Mutability
81
+
82
+ ```rust
83
+ // When you need mutation behind a shared reference
84
+
85
+ // Cell<T> — for Copy types, single-threaded
86
+ use std::cell::Cell;
87
+ let counter = Cell::new(0);
88
+ counter.set(counter.get() + 1); // Mutation through shared reference
89
+
90
+ // RefCell<T> — runtime borrow checking, single-threaded
91
+ use std::cell::RefCell;
92
+ let data = RefCell::new(vec![1, 2, 3]);
93
+ data.borrow_mut().push(4); // Panics if already borrowed
94
+
95
+ // Mutex<T> / RwLock<T> — thread-safe interior mutability
96
+ use std::sync::Mutex;
97
+ let shared = Mutex::new(Vec::new());
98
+ shared.lock().unwrap().push(42);
99
+
100
+ // Choose the right tool:
101
+ // Cell — Copy types, no overhead, single-threaded
102
+ // RefCell — any type, runtime cost, single-threaded, panics on violation
103
+ // Mutex — any type, blocking, multi-threaded
104
+ // RwLock — any type, read-heavy workloads, multi-threaded
105
+ // Atomic* — primitives, lock-free, multi-threaded
106
+ ```
107
+
108
+ ## Smart Pointers
109
+
110
+ ```rust
111
+ // Box<T> — heap allocation with single ownership
112
+ let boxed: Box<dyn Error> = Box::new(MyError::new("failed"));
113
+ // Use for: trait objects, recursive types, large values you want on heap
114
+
115
+ // Rc<T> — reference-counted, single-threaded shared ownership
116
+ use std::rc::Rc;
117
+ let shared = Rc::new(ExpensiveData::new());
118
+ let clone = Rc::clone(&shared); // Increments count, doesn't clone data
119
+ // Use for: graph structures, multiple owners in single-threaded code
120
+
121
+ // Arc<T> — atomic reference-counted, thread-safe shared ownership
122
+ use std::sync::Arc;
123
+ let shared = Arc::new(Config::load());
124
+ let handle = {
125
+ let shared = Arc::clone(&shared);
126
+ thread::spawn(move || process(&shared))
127
+ };
128
+ // Use for: shared immutable data across threads
129
+
130
+ // Cow<T> — clone-on-write
131
+ use std::borrow::Cow;
132
+ fn process(input: &str) -> Cow<'_, str> {
133
+ if input.contains("bad") {
134
+ Cow::Owned(input.replace("bad", "good"))
135
+ } else {
136
+ Cow::Borrowed(input) // No allocation when not needed
137
+ }
138
+ }
139
+ // Use for: avoiding allocation when mutation is conditional
140
+ ```
141
+
142
+ ## Common Borrow Checker Patterns
143
+
144
+ ### Splitting Borrows
145
+
146
+ ```rust
147
+ // The borrow checker tracks borrows per-field, not per-struct
148
+ struct State {
149
+ buffer: Vec<u8>,
150
+ index: usize,
151
+ }
152
+
153
+ impl State {
154
+ fn process(&mut self) {
155
+ // This works because buffer and index are separate fields
156
+ let buf = &self.buffer[self.index..];
157
+ self.index += buf.len();
158
+ }
159
+ }
160
+ ```
161
+
162
+ ### Temporary Lifetimes
163
+
164
+ ```rust
165
+ // Bad: temporary dropped while borrowed
166
+ // let r = &String::from("temp"); // Won't compile — temporary is dropped
167
+
168
+ // Good: bind to a variable to extend the lifetime
169
+ let s = String::from("temp");
170
+ let r = &s; // s lives as long as the scope
171
+ ```
172
+
173
+ ### Entry API for Maps
174
+
175
+ ```rust
176
+ use std::collections::HashMap;
177
+
178
+ let mut map = HashMap::new();
179
+
180
+ // Entry API avoids double lookup and borrow conflicts
181
+ map.entry("key")
182
+ .and_modify(|v| *v += 1)
183
+ .or_insert(0);
184
+ ```
185
+
186
+ ## Anti-Patterns
187
+
188
+ ```rust
189
+ // Never: Clone to satisfy the borrow checker without understanding why
190
+ let data = expensive_data.clone(); // Are you sure this is necessary?
191
+ // If you're cloning to fix a borrow error, first ask: can I restructure the code?
192
+
193
+ // Never: Leaking memory to avoid lifetimes
194
+ let leaked: &'static str = Box::leak(Box::new(String::from("forever")));
195
+ // This is almost never the right solution
196
+
197
+ // Never: Rc<RefCell<T>> as a default — it's a code smell
198
+ // If everything is Rc<RefCell<T>>, you've recreated garbage-collected mutable state
199
+ // Restructure to use ownership and borrowing properly first
200
+
201
+ // Never: Ignoring the borrow checker by reaching for unsafe
202
+ // If the borrow checker rejects your code, the design usually needs to change
203
+ // unsafe doesn't fix design problems — it hides them
204
+ ```