rust
Deep Rust patterns for project structure, async, error handling, unsafe, FFI, embedded, and testing
seniorv1.0.0senior/skills/rust
View source on GitHubRust
Project Structure
Workspace
[workspace]
members = ["crates/*"]
resolver = "2"
Cargo.toml
[package]
name = "my-crate"
version = "0.1.0"
edition = "2021"
[dependencies]
tokio = { version = "1", features = ["full"] }
thiserror = "1"
serde = { version = "1", features = ["derive"] }
Async Patterns (Tokio)
JoinSet
use tokio::task::JoinSet;
async fn run_tasks() {
let mut set = JoinSet::new();
for i in 0..10 {
set.spawn(async move { process(i).await });
}
while let Some(res) = set.join_next().await {
let result = res?;
}
}
CancellationToken
use tokio_util::sync::CancellationToken;
async fn worker(token: CancellationToken) {
tokio::select! {
_ = token.cancelled() => return,
result = do_work() => { /* handle result */ }
}
}
async fn main() {
let token = CancellationToken::new();
let h = tokio::spawn(worker(token.clone()));
token.cancel();
h.await;
}
Error Handling
thiserror
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AppError {
#[error("not found: {0}")]
NotFound(String),
#[error("validation failed")]
Validation { field: String, msg: String },
#[error(transparent)]
Io(#[from] std::io::Error),
}
anyhow
use anyhow::{Result, Context};
fn parse_config(path: &str) -> Result<Config> {
let content = std::fs::read_to_string(path)
.with_context(|| format!("failed to read {path}"))?;
Ok(serde_json::from_str(&content)?)
}
Unsafe Guidelines
- Minimize
unsafeblocks. Extract into safe abstractions. - Document safety invariants with
// SAFETY:comments. - Use
NonNull<T>instead of raw pointers where possible. - Validate references with
as_ref()/as_mut().
impl<T> MyVec<T> {
pub fn get(&self, index: usize) -> Option<&T> {
if index < self.len {
// SAFETY: index verified within bounds
Some(unsafe { &*self.ptr.add(index) })
} else {
None
}
}
}
FFI Patterns
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
extern "C" fn my_callback(data: *mut c_char) {
// SAFETY: C caller guarantees valid pointer
let s = unsafe { CStr::from_ptr(data) };
}
#[no_mangle]
pub extern "C" fn rust_function(input: *const c_char) -> *mut c_char {
let s = unsafe { CStr::from_ptr(input) }.to_str().unwrap();
CString::new(format!("hello {s}")).unwrap().into_raw()
}
Embedded (no_std)
#![no_std]
#![no_main]
use embedded_hal::digital::OutputPin;
use panic_halt as _;
#[arduino_hal::entry]
fn main() -> ! {
let dp = arduino_hal::Peripherals::take().unwrap();
let pins = arduino_hal::pins!(dp);
let mut led = pins.d13.into_output();
loop {
led.set_high();
arduino_hal::delay_ms(1000);
led.set_low();
arduino_hal::delay_ms(1000);
}
}
Testing
proptest
use proptest::prelude::*;
proptest! {
#[test]
fn parse_doesnt_crash(s in ".*") {
let _ = parse(&s);
}
}
criterion
use criterion::{black_box, Criterion};
fn bench_encode(c: &mut Criterion) {
c.bench_function("encode", |b| b.iter(|| encode(black_box(&data))));
}
Profiling
# Flamegraph
CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph
# perf
perf record --call-graph dwarf target/release/mybin
perf report
- Use
cargo-flamegraphfor quick flamegraphs. - Use
perffor detailed CPU analysis. - Valgrind for memory issues.