| name | rust-patterns |
| description | Rust patterns Axonix gets wrong repeatedly — check this before writing code. |
Rust Patterns — Quick Reference
Check this before writing or editing any Rust code. Each section has Bad/Good
examples. If you recognise the "Bad" pattern in your plan, stop and use the
"Good" version instead.
1. Ownership and Cloning
Vec<Box> vs Vec<Arc>
default_tools() returns Vec<Box<dyn AgentTool>>. Sub-agents need
Vec<Arc<dyn AgentTool>>. Convert at the call site, not inside the tool.
let tools = default_tools();
agent.with_tools(tools);
let arc_tools: Vec<Arc<dyn AgentTool>> = default_tools()
.into_iter()
.map(|b| Arc::from(b) as Arc<dyn AgentTool>)
.collect();
The arc_tools.clone() pattern (from build_tools())
Each sub-agent needs its own Vec<Arc<dyn AgentTool>>. Arc is cheap to clone
(increments a reference count). Call .clone() once per sub-agent.
agent_a.with_tools(arc_tools);
agent_b.with_tools(arc_tools);
agent_a.with_tools(arc_tools.clone());
agent_b.with_tools(arc_tools.clone());
agent_c.with_tools(arc_tools.clone());
Moving vs borrowing in match arms
let msg = match &response {
Ok(r) => r.text,
Err(e) => e.to_string(),
};
let msg = match &response {
Ok(r) => r.text.clone(),
Err(e) => e.to_string(),
};
2. Error Handling
Use ? not unwrap() in production paths
let content = std::fs::read_to_string("file.txt").unwrap();
fn load(path: &str) -> Result<String, std::io::Error> {
let content = std::fs::read_to_string(path)?;
Ok(content)
}
Box as return type
Use when the function can return multiple concrete error types.
fn run() -> Result<(), std::io::Error> { ... }
fn run() -> Result<(), Box<dyn std::error::Error>> {
let text = std::fs::read_to_string("cfg.toml")?;
let cfg: Config = toml::from_str(&text)?;
Ok(())
}
anyhow vs typed errors
use anyhow::{Context, Result};
fn start() -> Result<()> {
let val = risky_op().context("risky_op failed")?;
Ok(())
}
#[derive(Debug, thiserror::Error)]
enum ApiError {
#[error("rate limited: retry after {0}s")]
RateLimit(u64),
#[error("network error: {0}")]
Network(#[from] reqwest::Error),
}
3. Lifetimes and Closures
move || vs regular closure
let name = String::from("axonix");
std::thread::spawn(|| println!("{name}"));
let name = String::from("axonix");
std::thread::spawn(move || println!("{name}"));
|x| some_fn(x) vs some_fn alone
Passing a function pointer directly sometimes fails due to lifetime coercion
differences. Using an explicit closure wrapper always works.
items.iter().map(process_item)
items.iter().map(|item| process_item(item))
Capturing references vs owned values
let result = {
let tmp = expensive_compute();
move || tmp.value
};
let tmp = expensive_compute();
let result = move || tmp.value;
4. Common Compiler Errors — Before/After
E0382: use of moved value
What it means: A value was moved into one place and then used again.
let s = String::from("hello");
let a = s;
println!("{s}");
let s = String::from("hello");
let a = s.clone();
println!("{s}");
E0499: cannot borrow as mutable more than once
What it means: Two mutable borrows of the same value are alive simultaneously.
let mut v = vec![1, 2, 3];
let a = &mut v;
let b = &mut v;
let mut v = vec![1, 2, 3];
{ let a = &mut v; a.push(4); }
let b = &mut v;
E0716: temporary value dropped while borrowed
What it means: A reference points to a temporary that is immediately destroyed.
let r = expensive().result();
println!("{r}");
let tmp = expensive();
let r = tmp.result();
println!("{r}");
5. Cargo and Build Hygiene
Always run cargo fmt before committing
git commit -am "feat: add feature"
cargo fmt
git add -u
git commit -m "feat(scope): add feature"
Use clippy to catch issues early
cargo clippy --all-targets -- -D warnings
Adding a dependency
echo 'serde = "1"' >> Cargo.toml
cargo add serde --features derive
cargo build
#[allow(dead_code)] is a code smell
#[allow(dead_code)]
fn helper_never_used() { ... }
Written by Axonix — G-036 / Issue #42