
Rust Systems Programming
- 336 installs
- 61 repo stars
- Updated June 13, 2026
- manutej/luxor-claude-marketplace
Implement performant Rust binaries, services, and libraries using ownership, async I/O, error handling, and memory-safe systems patterns.
About
Covers Rust systems programming for backends and tools: ownership, lifetimes, Result patterns, async services, crate structure, testing, and deployment-minded code for CLIs, APIs, and agent sidecars requiring speed and safety.
- Ownership and borrowing
- Result and error traits
- Async Tokio services
- Safe concurrency primitives
- CLI and service scaffolding
Rust Systems Programming by the numbers
- 336 all-time installs (skills.sh)
- +20 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #33 of 121 Rust skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/manutej/luxor-claude-marketplace --skill rust-systems-programmingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 336 |
|---|---|
| repo stars | ★ 61 |
| Last updated | June 13, 2026 |
| Repository | manutej/luxor-claude-marketplace ↗ |
What it does
Implement performant Rust binaries, services, and libraries using ownership, async I/O, error handling, and memory-safe systems patterns.
Files
Rust Systems Programming
A comprehensive skill for building high-performance, memory-safe systems software using Rust. This skill covers ownership, borrowing, concurrency, async programming, unsafe code, FFI, and performance optimization for systems-level development.
When to Use This Skill
Use this skill when:
- Building systems software requiring memory safety without garbage collection
- Developing high-performance applications with zero-cost abstractions
- Writing concurrent or parallel programs with data race prevention
- Creating async/await applications for I/O-bound workloads (web servers, databases)
- Working with low-level code, FFI, or hardware interfaces
- Replacing C/C++ code with safer alternatives
- Building command-line tools, network services, or embedded systems
- Optimizing performance-critical sections of applications
- Creating libraries that guarantee memory safety at compile time
- Developing WebAssembly modules for near-native performance
Core Concepts
The Ownership Model
Rust's ownership system is the foundation of its memory safety guarantees:
Ownership Rules: 1. Each value in Rust has exactly one owner 2. When the owner goes out of scope, the value is dropped 3. Ownership can be transferred (moved) to new owners
Move Semantics:
struct MyStruct { s: u32 }
fn main() {
let mut x = MyStruct{ s: 5u32 };
let y = x; // Ownership moved from x to y
// x.s = 6; // ERROR: x is no longer valid
// println!("{}", x.s); // ERROR: cannot use x after move
}When a type doesn't implement Copy, assignment moves ownership rather than copying. This prevents double-free errors and use-after-move bugs at compile time.
For Copy Types:
let x = 5; // i32 implements Copy
let y = x; // x is copied, not moved
println!("{}", x); // OK: x is still validBorrowing and References
Borrowing allows temporary access to data without taking ownership:
Immutable Borrowing:
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1); // Borrow s1 immutably
println!("The length of '{}' is {}.", s1, len); // s1 still valid
}
fn calculate_length(s: &String) -> usize {
s.len() // Can read but not modify
}Mutable Borrowing:
Rust enforces exclusive mutable access to prevent data races:
fn main() {
let mut value = 3;
let borrow = &mut value; // Mutable borrow
*borrow += 1;
println!("{}", borrow); // 4
// value is accessible again after borrow ends
}Borrowing Rules:
- You can have either one mutable reference OR any number of immutable references
- References must always be valid (no dangling pointers)
- Mutable and immutable borrows cannot coexist
Common Borrowing Error:
fn main() {
let mut value = 3;
// Create a mutable borrow of `value`.
let borrow = &mut value;
let _sum = value + 1; // ERROR: cannot use `value` because
// it was mutably borrowed
println!("{}", borrow);
}Lifetimes
Lifetimes ensure references are always valid:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}The 'a lifetime parameter tells the compiler that the returned reference will be valid as long as both input references are valid.
Ownership Patterns for Sharing
Rc (Reference Counted) for Single-threaded Shared Ownership:
use std::cell::RefCell;
use std::rc::Rc;
struct MyStruct { s: u32 }
fn main() {
let mut x = Rc::new(RefCell::new(MyStruct{ s: 5u32 }));
let y = x.clone(); // Increment reference count
x.borrow_mut().s = 6; // Interior mutability via RefCell
println!("{}", x.borrow().s);
}Rc<T> provides shared ownership with reference counting. RefCell<T> enables interior mutability, enforcing borrow rules at runtime rather than compile time.
Arc (Atomic Reference Counted) for Thread-safe Sharing:
use std::sync::Arc;
use std::thread;
struct FancyNum {
num: u8,
}
fn main() {
let fancy_ref1 = Arc::new(FancyNum { num: 5 });
let fancy_ref2 = fancy_ref1.clone();
let x = thread::spawn(move || {
// `fancy_ref1` can be moved and has a `'static` lifetime
println!("child thread: {}", fancy_ref1.num);
});
x.join().expect("child thread should finish");
println!("main thread: {}", fancy_ref2.num);
}Arc<T> is the thread-safe version of Rc<T>, using atomic operations for reference counting.
Box for Heap Allocation
Box<T>Box<T> is an owning pointer that allocates T on the heap. Useful for:
- Recursive types with known size
- Large values that should not be copied on the stack
- Trait objects with dynamic dispatch
Concurrency Patterns
Threads and Message Passing
Rust prevents data races at compile time through its ownership system:
use std::thread;
use std::sync::mpsc;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
tx.send("Hello from thread").unwrap();
});
let message = rx.recv().unwrap();
println!("{}", message);
}Shared State with Mutex
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}Key Concurrency Types:
Mutex<T>: Mutual exclusion lock for shared mutable stateRwLock<T>: Reader-writer lock allowing multiple readers or one writerArc<T>: Atomic reference counting for thread-safe sharingmpsc: Multi-producer, single-consumer channels
Data Race Prevention
ThreadSanitizer Example:
static mut A: usize = 0;
fn main() {
let t = std::thread::spawn(|| {
unsafe { A += 1 };
});
unsafe { A += 1 };
t.join().unwrap();
}This code has a data race. ThreadSanitizer (enabled with RUSTFLAGS=-Zsanitizer=thread) detects concurrent access to static mutable data:
WARNING: ThreadSanitizer: data race (pid=10574)
Read of size 8 at 0x5632dfe3d030 by thread T1:
Previous write of size 8 at 0x5632dfe3d030 by main thread:Async Programming
Async/Await Basics
Async programming in Rust allows concurrent I/O without blocking threads:
async fn foo(n: usize) {
if n > 0 {
Box::pin(foo(n - 1)).await;
}
}Recursive async functions require Box::pin() to give the future a known size.
Async Closures
Async Closure with Move Semantics:
fn force_fnonce<T: async FnOnce()>(t: T) -> T { t }
let x = String::new();
let c = force_fnonce(async move || {
println!("{x}");
});When constrained to AsyncFnOnce, the closure captures by move to ensure proper ownership.
Async Closure Borrowing:
let x = &1i32; // Lifetime '1
let c = async move || {
println!("{:?}", *x);
// Even though the closure moves x, we're only capturing *x,
// so the inner coroutine can reborrow the data for its original lifetime.
};Mutable Borrowing in Async Closures:
let mut x = 1i32;
let c = async || {
x = 1;
// The parent borrows `x` mutably.
// When we call `c()`, we implicitly autoref for `AsyncFnMut::async_call_mut`.
// The inner coroutine captures with the lifetime of the coroutine-closure.
};Common Async Errors
E0373: Async block capturing short-lived variable:
use std::future::Future;
async fn f() {
let v = vec![1, 2, 3i32];
spawn(async { //~ ERROR E0373
println!("{:?}", v) // v might go out of scope before async block runs
});
}
fn spawn<F: Future + Send + 'static>(future: F) {
unimplemented!()
}Solution: Move the variable into the async block:
spawn(async move {
println!("{:?}", v)
})Unsafe Code and FFI
When to Use Unsafe
Rust's unsafe keyword allows operations that the compiler cannot verify:
1. Dereferencing raw pointers 2. Calling unsafe functions or methods 3. Accessing or modifying mutable static variables 4. Implementing unsafe traits 5. Accessing fields of unions
Unsafe Dereference Example:
macro_rules! unsafe_deref {
() => {
*(&() as *const ())
};
}Memory Safety with Unsafe
Unsafe Sync Implementation:
use std::cell::Cell;
struct NotThreadSafe<T> {
value: Cell<T>,
}
unsafe impl<T> Sync for NotThreadSafe<T> {}
static A: NotThreadSafe<usize> = NotThreadSafe { value : Cell::new(1) };
static B: &'static NotThreadSafe<usize> = &A; // ok!This is unsafe because you must manually ensure thread safety. Cell is not Sync by default, so this implementation requires careful reasoning.
FFI (Foreign Function Interface)
Calling C Functions from Rust:
use std::mem;
#[link(name = "foo")]
extern "C" {
fn do_twice(f: unsafe extern "C" fn(i32) -> i32, arg: i32) -> i32;
}
unsafe extern "C" fn add_one(x: i32) -> i32 {
x + 1
}
unsafe extern "C" fn add_two(x: i64) -> i64 {
x + 2
}
fn main() {
let answer = unsafe { do_twice(add_one, 5) };
println!("The answer is: {}", answer);
// Type-mismatched call (unsafe):
println!("With CFI enabled, you should not see the next answer");
let f: unsafe extern "C" fn(i32) -> i32 = unsafe {
mem::transmute::<*const u8, unsafe extern "C" fn(i32) -> i32>(add_two as *const u8)
};
let next_answer = unsafe { do_twice(f, 5) };
println!("The next answer is: {}", next_answer);
}Control Flow Integrity (CFI): With CFI enabled, the type-mismatched transmute causes program termination, preventing control flow hijacking.
Inline Assembly for System Calls:
static UNMAP_BASE: usize;
const MEM_RELEASE: usize;
static VirtualFree: usize;
const OffPtr: usize;
const OffFn: usize;
core::arch::asm!("
push {free_type}
push {free_size}
push {base}
mov eax, fs:[30h]
mov eax, [eax+8h]
add eax, {off_fn}
mov [eax-{off_fn}+{off_ptr}], eax
push eax
jmp {virtual_free}
",
off_ptr = const OffPtr,
off_fn = const OffFn,
free_size = const 0,
free_type = const MEM_RELEASE,
virtual_free = sym VirtualFree,
base = sym UNMAP_BASE,
options(noreturn),
);This demonstrates direct system calls using inline assembly for Windows memory deallocation.
Error Handling
Result and Option Types
Rust uses Result<T, E> and Option<T> for error handling:
fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err("Division by zero".to_string())
} else {
Ok(a / b)
}
}
fn main() {
match divide(10, 2) {
Ok(result) => println!("Result: {}", result),
Err(e) => println!("Error: {}", e),
}
}The ? Operator
fn process_file(path: &str) -> Result<String, std::io::Error> {
let content = std::fs::read_to_string(path)?;
Ok(content.to_uppercase())
}The ? operator propagates errors up the call stack, similar to exceptions but explicit in the type signature.
Custom Error Types
use std::fmt;
#[derive(Debug)]
enum AppError {
IoError(std::io::Error),
ParseError(std::num::ParseIntError),
Custom(String),
}
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AppError::IoError(e) => write!(f, "IO error: {}", e),
AppError::ParseError(e) => write!(f, "Parse error: {}", e),
AppError::Custom(msg) => write!(f, "Error: {}", msg),
}
}
}
impl std::error::Error for AppError {}Memory Safety and Sanitizers
AddressSanitizer
Detecting Stack Buffer Overflow:
fn main() {
let xs = [0, 1, 2, 3];
let _y = unsafe { *xs.as_ptr().offset(4) };
}Build with AddressSanitizer:
$ export RUSTFLAGS=-Zsanitizer=address RUSTDOCFLAGS=-Zsanitizer=address
$ cargo run -Zbuild-std --target x86_64-unknown-linux-gnuOutput:
==37882==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffe400e6250
READ of size 4 at 0x7ffe400e6250 thread T0
#0 0x5609a841fb1f in example::main::h628ffc6626ed85b2 /.../src/main.rs:3:23
Address 0x7ffe400e6250 is located in stack of thread T0 at offset 48 in frame
This frame has 1 object(s):
[32, 48) 'xs' (line 2) <== Memory access at offset 48 overflows this variableDetecting Heap Buffer Overflow:
fn main() {
let xs = vec![0, 1, 2, 3];
let _y = unsafe { *xs.as_ptr().offset(4) };
}Detecting Use-After-Scope:
static mut P: *mut usize = std::ptr::null_mut();
fn main() {
unsafe {
{
let mut x = 0;
P = &mut x;
}
std::ptr::write_volatile(P, 123); // P points to dropped variable
}
}AddressSanitizer output:
==39249==ERROR: AddressSanitizer: stack-use-after-scope on address 0x7ffc7ed3e1a0
WRITE of size 8 at 0x7ffc7ed3e1a0 thread T0Performance Optimization
Zero-Cost Abstractions
Rust provides high-level abstractions without runtime overhead:
// Iterator chains are optimized to simple loops
let sum: i32 = (1..100)
.filter(|x| x % 2 == 0)
.map(|x| x * x)
.sum();The compiler optimizes this to a tight loop equivalent to manual iteration.
Inlining and Monomorphization
Generic functions are monomorphized (specialized) for each concrete type:
#[inline]
fn add<T: std::ops::Add<Output = T>>(a: T, b: T) -> T {
a + b
}
let x = add(5, 3); // Specialized for i32
let y = add(5.0, 3.0); // Specialized for f64Smart Pointer Overhead
Different smart pointers have different costs:
Box<T>: Single heap allocation, no overheadRc<T>: Reference counting, small overhead per clone/dropArc<T>: Atomic reference counting, higher overhead for thread safetyMutex<T>: Lock acquisition overheadRefCell<T>: Runtime borrow checking overhead
Avoiding Allocations
// Bad: Allocates a new String
fn greet_bad(name: &str) -> String {
format!("Hello, {}", name)
}
// Good: Returns a reference, no allocation
fn greet_good(name: &str) -> impl std::fmt::Display + '_ {
format_args!("Hello, {}", name)
}Common Patterns and Idioms
Builder Pattern
struct Config {
host: String,
port: u16,
timeout: u64,
}
impl Config {
fn builder() -> ConfigBuilder {
ConfigBuilder::default()
}
}
#[derive(Default)]
struct ConfigBuilder {
host: Option<String>,
port: Option<u16>,
timeout: Option<u64>,
}
impl ConfigBuilder {
fn host(mut self, host: impl Into<String>) -> Self {
self.host = Some(host.into());
self
}
fn port(mut self, port: u16) -> Self {
self.port = Some(port);
self
}
fn build(self) -> Config {
Config {
host: self.host.unwrap_or_else(|| "localhost".to_string()),
port: self.port.unwrap_or(8080),
timeout: self.timeout.unwrap_or(30),
}
}
}Newtype Pattern
struct UserId(u64);
struct PostId(u64);
fn get_user(id: UserId) -> User { /* ... */ }
// This won't compile: type safety!
// get_user(PostId(42));RAII (Resource Acquisition Is Initialization)
struct FileGuard {
file: std::fs::File,
}
impl FileGuard {
fn new(path: &str) -> std::io::Result<Self> {
Ok(FileGuard {
file: std::fs::File::create(path)?,
})
}
}
impl Drop for FileGuard {
fn drop(&mut self) {
println!("File closed automatically");
}
}Closure Patterns and Errors
E0500: Closure Borrowing Conflict
Problem:
fn you_know_nothing(jon_snow: &mut i32) {
let nights_watch = &jon_snow;
let starks = || {
*jon_snow = 3; // error: closure requires unique access to `jon_snow`
// but it is already borrowed
};
println!("{}", nights_watch);
}Solution:
fn you_know_nothing(jon_snow: &mut i32) {
let nights_watch = &jon_snow;
println!("{}", nights_watch); // Use the borrow first
// Borrow ends here (non-lexical lifetimes)
let starks = || {
*jon_snow = 3; // Now OK
};
}E0502: Mutable and Immutable Borrows
Problem:
fn bar(x: &mut i32) {}
fn foo(a: &mut i32) {
let y = &a;
bar(a); // Error: cannot borrow as mutable while borrowed as immutable
println!("{}", y);
}Solution:
fn bar(x: &mut i32) {}
fn foo(a: &mut i32) {
bar(a); // Mutable borrow first
let y = &a; // Immutable borrow after mutable borrow ends
println!("{}", y);
}E0505: Move of Borrowed Value
Problem:
struct Value {}
fn borrow(val: &Value) {}
fn eat(val: Value) {}
fn main() {
let x = Value{};
let _ref_to_val: &Value = &x;
eat(x); // Error: cannot move x while borrowed
borrow(_ref_to_val);
}E0524: Concurrent Mutable Borrows in Closures
Problem:
fn set(x: &mut isize) {
*x += 4;
}
fn dragoooon(x: &mut isize) {
let mut c1 = || set(x);
let mut c2 = || set(x); // error: two closures trying to borrow mutably
c2();
c1();
}Solution 1: Sequential Execution (Non-Lexical Lifetimes)
fn set(x: &mut isize) {
*x += 4;
}
fn dragoooon(x: &mut isize) {
{
let mut c1 = || set(&mut *x);
c1();
} // `c1` has been dropped here so we're free to use `x` again!
let mut c2 = || set(&mut *x);
c2();
}Solution 2: Rc + RefCell for Shared Mutable Access
use std::rc::Rc;
use std::cell::RefCell;
fn set(x: &mut isize) {
*x += 4;
}
fn dragoooon(x: &mut isize) {
let x = Rc::new(RefCell::new(x));
let y = Rc::clone(&x);
let mut c1 = || { let mut x2 = x.borrow_mut(); set(&mut x2); };
let mut c2 = || { let mut x2 = y.borrow_mut(); set(&mut x2); };
c2();
c1();
}E0507: Moving Borrowed Content
Problem:
use std::cell::RefCell;
struct TheDarkKnight;
impl TheDarkKnight {
fn nothing_is_true(self) {}
}
fn main() {
let x = RefCell::new(TheDarkKnight);
x.borrow().nothing_is_true(); // Error: cannot move out of borrowed content
}Solution 1: Take a Reference
impl TheDarkKnight {
fn nothing_is_true(&self) {} // Change to &self
}
fn main() {
let x = RefCell::new(TheDarkKnight);
x.borrow().nothing_is_true(); // OK
}Solution 2: Reclaim Ownership
fn main() {
let x = RefCell::new(TheDarkKnight);
let x = x.into_inner(); // Reclaim ownership
x.nothing_is_true(); // OK
}Production Patterns
Structured Logging
use tracing::{info, warn, error, instrument};
#[instrument]
async fn process_request(id: u64) -> Result<(), Error> {
info!(request_id = id, "Processing request");
match do_work(id).await {
Ok(_) => {
info!("Request completed successfully");
Ok(())
}
Err(e) => {
error!(error = ?e, "Request failed");
Err(e)
}
}
}Configuration Management
use serde::Deserialize;
#[derive(Deserialize)]
struct AppConfig {
database_url: String,
server_port: u16,
log_level: String,
}
fn load_config() -> Result<AppConfig, config::ConfigError> {
config::Config::builder()
.add_source(config::File::with_name("config"))
.add_source(config::Environment::with_prefix("APP"))
.build()?
.try_deserialize()
}Graceful Shutdown
use tokio::signal;
async fn shutdown_signal() {
let ctrl_c = async {
signal::ctrl_c()
.await
.expect("failed to install Ctrl+C handler");
};
#[cfg(unix)]
let terminate = async {
signal::unix::signal(signal::unix::SignalKind::terminate())
.expect("failed to install signal handler")
.recv()
.await;
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
_ = ctrl_c => {},
_ = terminate => {},
}
println!("Shutdown signal received, starting graceful shutdown");
}Connection Pooling
use deadpool_postgres::{Config, Pool, Runtime};
use tokio_postgres::NoTls;
async fn create_pool() -> Pool {
let mut cfg = Config::new();
cfg.host = Some("localhost".to_string());
cfg.dbname = Some("mydb".to_string());
cfg.create_pool(Some(Runtime::Tokio1), NoTls)
.expect("Failed to create pool")
}
async fn query_user(pool: &Pool, id: i64) -> Result<User, Error> {
let client = pool.get().await?;
let row = client
.query_one("SELECT * FROM users WHERE id = $1", &[&id])
.await?;
Ok(User::from_row(row))
}Best Practices
API Design
1. Use References by Default: Accept &T instead of T unless you need ownership 2. Return Owned Types: Return String instead of &str for simpler APIs 3. Implement Standard Traits: Debug, Clone, PartialEq, Send, Sync 4. Use `Into<T>` for Flexibility: fn set_name(&mut self, name: impl Into<String>) 5. Leverage the Type System: Use newtypes, enums, and Result for correctness
Testing Patterns
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_addition() {
assert_eq!(add(2, 2), 4);
}
#[test]
#[should_panic(expected = "division by zero")]
fn test_divide_by_zero() {
divide(10, 0);
}
#[tokio::test]
async fn test_async_function() {
let result = fetch_data().await;
assert!(result.is_ok());
}
}Documentation
/// Calculates the sum of two numbers.
///
/// # Examples
///
/// ```
/// let result = add(2, 2);
/// assert_eq!(result, 4);
/// ```
///
/// # Panics
///
/// This function will panic if the result overflows.
pub fn add(a: i32, b: i32) -> i32 {
a.checked_add(b).expect("overflow in add")
}Dependency Management
[dependencies]
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
tracing = "0.1"
[dev-dependencies]
criterion = "0.5"
[profile.release]
lto = true
codegen-units = 1Troubleshooting
Common Borrow Checker Issues
Issue: "Cannot borrow as mutable because it is also borrowed as immutable" Solution: Ensure mutable and immutable borrows don't overlap in scope
Issue: "Cannot move out of borrowed content" Solution: Clone the value, take a reference, or use Rc<RefCell<T>>
Issue: "Lifetime issues with references" Solution: Add explicit lifetime annotations or restructure code to avoid complex lifetimes
Performance Issues
Issue: Slow compilation times Solution: Use incremental compilation, reduce generic instantiations, use cargo check
Issue: Runtime performance slower than expected Solution: Profile with perf, enable LTO, check for unnecessary allocations/clones
Issue: High memory usage Solution: Use references instead of clones, consider streaming data, profile with Valgrind
Concurrency Issues
Issue: Deadlocks with multiple mutexes Solution: Always acquire locks in the same order, use try_lock with timeout
Issue: Data races in unsafe code Solution: Run with ThreadSanitizer, carefully review unsafe blocks
Issue: Async tasks not making progress Solution: Check for blocking operations in async code, use spawn_blocking for CPU-bound work
Quick Reference
Smart Pointer Cheat Sheet
Box<T> - Heap allocation, single owner
Rc<T> - Reference counted, single-threaded
Arc<T> - Atomic reference counted, thread-safe
Mutex<T> - Mutual exclusion lock
RwLock<T> - Reader-writer lock
RefCell<T> - Runtime borrow checking
Cell<T> - Interior mutability for Copy typesCommon Trait Implementations
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct MyStruct {
field: String,
}
impl Default for MyStruct {
fn default() -> Self {
Self {
field: String::new(),
}
}
}
impl std::fmt::Display for MyStruct {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "MyStruct({})", self.field)
}
}Async Runtime Comparison
Tokio: Full-featured, most popular, excellent ecosystem
async-std: Mirrors std library API, simpler for beginners
smol: Lightweight, minimal dependenciesResources
- The Rust Book: https://doc.rust-lang.org/book/
- Rust by Example: https://doc.rust-lang.org/rust-by-example/
- The Rustonomicon (unsafe Rust): https://doc.rust-lang.org/nomicon/
- Async Book: https://rust-lang.github.io/async-book/
- Rust Performance Book: https://nnethercote.github.io/perf-book/
- Crate Documentation: https://docs.rs/
---
Skill Version: 1.0.0 Last Updated: October 2025 Skill Category: Systems Programming, Performance, Memory Safety Context7 Integration: Rust documentation and error code examples
Rust Systems Programming Examples
Comprehensive examples for real-world Rust development, covering ownership, concurrency, async programming, unsafe code, and production patterns.
Table of Contents
1. Ownership and Borrowing 2. Concurrency Patterns 3. Async Programming 4. Error Handling 5. Unsafe Code and FFI 6. Memory Safety and Sanitizers 7. Performance Optimization 8. Production Web Server 9. CLI Application 10. Database Connection Pool
---
Ownership and Borrowing
Example 1: Basic Ownership and Move Semantics
Scenario: Understanding move semantics for non-Copy types
struct UserData {
username: String,
email: String,
age: u32,
}
fn main() {
// Create a user
let user1 = UserData {
username: String::from("alice"),
email: String::from("alice@example.com"),
age: 30,
};
// Move ownership to user2
let user2 = user1;
// Error: user1 is no longer valid
// println!("User1: {}", user1.username);
// user2 owns the data now
println!("User2: {}", user2.username);
}Key Points:
UserDatadoesn't implementCopy, so assignment moves ownership- After the move,
user1is invalid and cannot be used - This prevents double-free errors at compile time
Example 2: Immutable and Mutable Borrowing
Scenario: Reading and modifying data without transferring ownership
struct BankAccount {
balance: f64,
}
impl BankAccount {
fn new(initial: f64) -> Self {
BankAccount { balance: initial }
}
fn deposit(&mut self, amount: f64) {
self.balance += amount;
}
fn get_balance(&self) -> f64 {
self.balance
}
}
fn apply_interest(account: &mut BankAccount, rate: f64) {
let interest = account.get_balance() * rate;
account.deposit(interest);
}
fn display_balance(account: &BankAccount) {
println!("Balance: ${:.2}", account.get_balance());
}
fn main() {
let mut account = BankAccount::new(1000.0);
// Immutable borrow for reading
display_balance(&account);
// Mutable borrow for modification
apply_interest(&mut account, 0.05);
// Can borrow again after previous borrow ends
display_balance(&account);
}Key Points:
- Immutable borrows (
&T) allow reading but not modifying - Mutable borrows (
&mut T) allow modification but must be exclusive - Original owner retains ownership and can use the value after borrows end
Example 3: Shared Ownership with Rc and RefCell
Scenario: Multiple ownership with interior mutability (single-threaded)
use std::cell::RefCell;
use std::rc::Rc;
struct Document {
title: String,
content: String,
view_count: u32,
}
impl Document {
fn new(title: String) -> Self {
Document {
title,
content: String::new(),
view_count: 0,
}
}
fn increment_views(&mut self) {
self.view_count += 1;
}
fn add_content(&mut self, text: &str) {
self.content.push_str(text);
}
}
struct Editor {
doc: Rc<RefCell<Document>>,
}
impl Editor {
fn edit(&self, text: &str) {
self.doc.borrow_mut().add_content(text);
}
}
struct Viewer {
doc: Rc<RefCell<Document>>,
}
impl Viewer {
fn view(&self) {
let mut doc = self.doc.borrow_mut();
doc.increment_views();
println!("Viewing: {} (Views: {})", doc.title, doc.view_count);
println!("Content: {}", doc.content);
}
}
fn main() {
let doc = Rc::new(RefCell::new(Document::new("My Document".to_string())));
let editor = Editor {
doc: Rc::clone(&doc),
};
let viewer = Viewer {
doc: Rc::clone(&doc),
};
editor.edit("Hello, ");
editor.edit("world!");
viewer.view();
viewer.view();
println!("Reference count: {}", Rc::strong_count(&doc));
}Key Points:
Rc<T>provides shared ownership with reference countingRefCell<T>enables interior mutability with runtime borrow checking- Use for single-threaded scenarios requiring shared mutable state
Example 4: Closure Borrowing Patterns
Scenario: Understanding closure capture modes and borrowing conflicts
fn closure_immutable_borrow() {
let data = vec![1, 2, 3, 4, 5];
// Closure borrows data immutably
let print_sum = || {
let sum: i32 = data.iter().sum();
println!("Sum: {}", sum);
};
print_sum();
print_sum();
// data is still accessible
println!("Original data: {:?}", data);
}
fn closure_mutable_borrow() {
let mut counter = 0;
// Closure borrows counter mutably
let mut increment = || {
counter += 1;
};
increment();
increment();
// Cannot access counter here while increment holds mutable borrow
// println!("{}", counter); // ERROR
drop(increment); // Explicitly drop to end borrow
// Now we can access counter
println!("Final count: {}", counter);
}
fn closure_move_semantics() {
let data = vec![1, 2, 3];
// Move data into closure
let process = move || {
println!("Processing: {:?}", data);
data.len()
};
let len = process();
println!("Length: {}", len);
// Cannot access data here - it was moved
// println!("{:?}", data); // ERROR
}
fn main() {
closure_immutable_borrow();
closure_mutable_borrow();
closure_move_semantics();
}Key Points:
- Closures automatically borrow variables from their environment
- Use
movekeyword to transfer ownership into closure - Mutable borrows in closures must be exclusive
---
Concurrency Patterns
Example 5: Thread-Safe Shared State with Arc and Mutex
Scenario: Multiple threads incrementing a shared counter safely
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
struct Metrics {
requests: u64,
errors: u64,
}
impl Metrics {
fn new() -> Self {
Metrics {
requests: 0,
errors: 0,
}
}
fn record_request(&mut self, is_error: bool) {
self.requests += 1;
if is_error {
self.errors += 1;
}
}
fn error_rate(&self) -> f64 {
if self.requests == 0 {
0.0
} else {
(self.errors as f64 / self.requests as f64) * 100.0
}
}
}
fn main() {
let metrics = Arc::new(Mutex::new(Metrics::new()));
let mut handles = vec![];
// Spawn 10 worker threads
for i in 0..10 {
let metrics = Arc::clone(&metrics);
let handle = thread::spawn(move || {
for j in 0..100 {
// Simulate work
thread::sleep(Duration::from_millis(1));
// Record metric (10% error rate)
let is_error = (i + j) % 10 == 0;
let mut m = metrics.lock().unwrap();
m.record_request(is_error);
}
});
handles.push(handle);
}
// Wait for all threads to complete
for handle in handles {
handle.join().unwrap();
}
// Print final metrics
let m = metrics.lock().unwrap();
println!("Total requests: {}", m.requests);
println!("Total errors: {}", m.errors);
println!("Error rate: {:.2}%", m.error_rate());
}Key Points:
Arc<T>enables thread-safe shared ownership with atomic reference countingMutex<T>provides mutual exclusion for safe mutable access- Lock guard automatically releases the lock when dropped
Example 6: Message Passing with Channels
Scenario: Producer-consumer pattern with multiple workers
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
enum Task {
Process(String),
Shutdown,
}
struct Worker {
id: usize,
thread: thread::JoinHandle<()>,
}
impl Worker {
fn new(id: usize, receiver: std::sync::Arc<std::sync::Mutex<mpsc::Receiver<Task>>>) -> Self {
let thread = thread::spawn(move || {
loop {
let task = receiver.lock().unwrap().recv().unwrap();
match task {
Task::Process(data) => {
println!("Worker {} processing: {}", id, data);
thread::sleep(Duration::from_millis(100));
println!("Worker {} finished: {}", id, data);
}
Task::Shutdown => {
println!("Worker {} shutting down", id);
break;
}
}
}
});
Worker { id, thread }
}
}
struct ThreadPool {
workers: Vec<Worker>,
sender: mpsc::Sender<Task>,
}
impl ThreadPool {
fn new(size: usize) -> Self {
let (sender, receiver) = mpsc::channel();
let receiver = std::sync::Arc::new(std::sync::Mutex::new(receiver));
let mut workers = Vec::with_capacity(size);
for id in 0..size {
workers.push(Worker::new(id, std::sync::Arc::clone(&receiver)));
}
ThreadPool { workers, sender }
}
fn execute(&self, data: String) {
self.sender.send(Task::Process(data)).unwrap();
}
fn shutdown(self) {
for _ in &self.workers {
self.sender.send(Task::Shutdown).unwrap();
}
for worker in self.workers {
worker.thread.join().unwrap();
}
}
}
fn main() {
let pool = ThreadPool::new(4);
for i in 0..20 {
pool.execute(format!("Task {}", i));
}
println!("All tasks submitted, shutting down...");
pool.shutdown();
println!("All workers finished");
}Key Points:
- Channels enable safe message passing between threads
- Multiple producers can send to a single receiver
- Shared receiver wrapped in
Arc<Mutex<>>for multiple consumers
Example 7: Reader-Writer Lock Pattern
Scenario: Multiple readers, single writer with RwLock
use std::sync::{Arc, RwLock};
use std::thread;
use std::time::Duration;
struct Cache {
data: RwLock<std::collections::HashMap<String, String>>,
}
impl Cache {
fn new() -> Self {
Cache {
data: RwLock::new(std::collections::HashMap::new()),
}
}
fn get(&self, key: &str) -> Option<String> {
// Read lock allows multiple concurrent readers
let data = self.data.read().unwrap();
data.get(key).cloned()
}
fn set(&self, key: String, value: String) {
// Write lock ensures exclusive access
let mut data = self.data.write().unwrap();
data.insert(key, value);
}
}
fn main() {
let cache = Arc::new(Cache::new());
// Pre-populate cache
cache.set("user:1".to_string(), "Alice".to_string());
cache.set("user:2".to_string(), "Bob".to_string());
let mut handles = vec![];
// Spawn 10 reader threads
for i in 0..10 {
let cache = Arc::clone(&cache);
let handle = thread::spawn(move || {
for _ in 0..5 {
let key = format!("user:{}", (i % 2) + 1);
if let Some(value) = cache.get(&key) {
println!("Reader {} got: {} = {}", i, key, value);
}
thread::sleep(Duration::from_millis(10));
}
});
handles.push(handle);
}
// Spawn 2 writer threads
for i in 0..2 {
let cache = Arc::clone(&cache);
let handle = thread::spawn(move || {
for j in 0..3 {
let key = format!("user:{}", i + 3);
let value = format!("Writer{}Value{}", i, j);
cache.set(key.clone(), value.clone());
println!("Writer {} set: {} = {}", i, key, value);
thread::sleep(Duration::from_millis(50));
}
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Final cache size: {}", cache.data.read().unwrap().len());
}Key Points:
RwLockallows multiple concurrent readers or one exclusive writer- Better performance than
Mutexfor read-heavy workloads - Readers don't block each other, only writers
---
Async Programming
Example 8: Async HTTP Client with Tokio
Scenario: Fetching multiple URLs concurrently
use tokio;
use std::time::Duration;
#[derive(Debug)]
struct FetchResult {
url: String,
status: String,
elapsed: Duration,
}
async fn fetch_url(url: &str) -> Result<FetchResult, Box<dyn std::error::Error>> {
let start = std::time::Instant::now();
// Simulate HTTP fetch
tokio::time::sleep(Duration::from_millis(100)).await;
let result = FetchResult {
url: url.to_string(),
status: "200 OK".to_string(),
elapsed: start.elapsed(),
};
Ok(result)
}
async fn fetch_all(urls: Vec<&str>) -> Vec<FetchResult> {
let mut tasks = vec![];
for url in urls {
tasks.push(tokio::spawn(async move {
fetch_url(url).await
}));
}
let mut results = vec![];
for task in tasks {
match task.await {
Ok(Ok(result)) => results.push(result),
Ok(Err(e)) => eprintln!("Fetch error: {}", e),
Err(e) => eprintln!("Task error: {}", e),
}
}
results
}
#[tokio::main]
async fn main() {
let urls = vec![
"https://example.com/api/users",
"https://example.com/api/posts",
"https://example.com/api/comments",
"https://example.com/api/profile",
"https://example.com/api/settings",
];
println!("Fetching {} URLs...", urls.len());
let start = std::time::Instant::now();
let results = fetch_all(urls).await;
println!("\nCompleted {} fetches in {:?}", results.len(), start.elapsed());
for result in results {
println!(" {} - {} ({:?})", result.url, result.status, result.elapsed);
}
}Key Points:
tokio::spawncreates concurrent async tasks- Tasks run concurrently on the async runtime
- Total time is roughly the slowest individual fetch, not the sum
Example 9: Async Stream Processing
Scenario: Processing items from an async stream
use tokio;
use tokio::time::{sleep, Duration};
use futures::stream::{self, StreamExt};
async fn process_item(item: i32) -> i32 {
// Simulate async processing
sleep(Duration::from_millis(100)).await;
item * 2
}
async fn stream_example() {
let items = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Create stream from iterator
let stream = stream::iter(items);
// Process items concurrently (buffer up to 4 at a time)
let results: Vec<i32> = stream
.map(|item| async move {
process_item(item).await
})
.buffer_unordered(4)
.collect()
.await;
println!("Processed results: {:?}", results);
}
#[tokio::main]
async fn main() {
let start = std::time::Instant::now();
stream_example().await;
println!("Completed in {:?}", start.elapsed());
}Key Points:
- Streams enable async iteration over sequences
buffer_unorderedprocesses multiple items concurrently- Preserves async efficiency while processing collections
Example 10: Async Closure Patterns
Scenario: Using async closures with different capture modes
use tokio;
async fn async_closure_examples() {
// Immutable capture
let data = vec![1, 2, 3, 4, 5];
let process_immut = async || {
let sum: i32 = data.iter().sum();
println!("Sum: {}", sum);
sum
};
let result = process_immut.await;
println!("Result: {}", result);
// Move capture
let owned_data = String::from("Hello, async world!");
let process_move = async move || {
println!("Message: {}", owned_data);
owned_data.len()
};
let len = process_move.await;
println!("Length: {}", len);
// owned_data is no longer accessible
// Mutable capture (requires AsyncFnMut)
let mut counter = 0;
let mut increment = async || {
counter += 1;
println!("Counter: {}", counter);
};
increment.await;
increment.await;
increment.await;
}
#[tokio::main]
async fn main() {
async_closure_examples().await;
}Key Points:
- Async closures work similarly to regular closures
- Use
moveto transfer ownership into async closure - Mutable captures require special handling
---
Error Handling
Example 11: Custom Error Types with thiserror
Scenario: Building a robust error handling system
use std::fmt;
#[derive(Debug)]
enum AppError {
Io(std::io::Error),
Parse(std::num::ParseIntError),
NotFound(String),
Invalid(String),
}
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AppError::Io(e) => write!(f, "IO error: {}", e),
AppError::Parse(e) => write!(f, "Parse error: {}", e),
AppError::NotFound(msg) => write!(f, "Not found: {}", msg),
AppError::Invalid(msg) => write!(f, "Invalid: {}", msg),
}
}
}
impl std::error::Error for AppError {}
impl From<std::io::Error> for AppError {
fn from(err: std::io::Error) -> Self {
AppError::Io(err)
}
}
impl From<std::num::ParseIntError> for AppError {
fn from(err: std::num::ParseIntError) -> Self {
AppError::Parse(err)
}
}
fn read_config(path: &str) -> Result<Config, AppError> {
let content = std::fs::read_to_string(path)?;
if content.is_empty() {
return Err(AppError::Invalid("Config file is empty".to_string()));
}
parse_config(&content)
}
fn parse_config(content: &str) -> Result<Config, AppError> {
let parts: Vec<&str> = content.split('=').collect();
if parts.len() != 2 {
return Err(AppError::Invalid("Invalid config format".to_string()));
}
let port: u16 = parts[1].trim().parse()?;
Ok(Config { port })
}
struct Config {
port: u16,
}
fn main() {
match read_config("config.txt") {
Ok(config) => println!("Loaded config: port={}", config.port),
Err(e) => eprintln!("Error: {}", e),
}
}Key Points:
- Custom error types centralize error handling
Fromimplementations enable?operatorDisplayandErrortraits make errors user-friendly
Example 12: Error Propagation with Context
Scenario: Adding context to errors as they propagate
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
fn process_user_data(user_id: u64) -> Result<String> {
let raw_data = fetch_user(user_id)
.map_err(|e| format!("Failed to fetch user {}: {}", user_id, e))?;
let parsed = parse_data(&raw_data)
.map_err(|e| format!("Failed to parse data for user {}: {}", user_id, e))?;
let validated = validate_data(&parsed)
.map_err(|e| format!("Validation failed for user {}: {}", user_id, e))?;
Ok(validated)
}
fn fetch_user(user_id: u64) -> Result<String> {
if user_id == 0 {
Err("Invalid user ID".into())
} else {
Ok(format!("user_data_{}", user_id))
}
}
fn parse_data(data: &str) -> Result<String> {
if data.is_empty() {
Err("Empty data".into())
} else {
Ok(data.to_uppercase())
}
}
fn validate_data(data: &str) -> Result<String> {
if data.len() < 5 {
Err("Data too short".into())
} else {
Ok(data.to_string())
}
}
fn main() {
match process_user_data(42) {
Ok(data) => println!("Processed: {}", data),
Err(e) => eprintln!("Error: {}", e),
}
}Key Points:
map_erradds context to errors- Error messages form a trace of what went wrong
- Makes debugging much easier in production
---
Unsafe Code and FFI
Example 13: Raw Pointer Manipulation
Scenario: Working with raw pointers for performance-critical code
fn unsafe_pointer_example() {
let mut value = 42;
// Create raw pointers
let r1: *const i32 = &value;
let r2: *mut i32 = &mut value;
unsafe {
// Dereference raw pointers
println!("r1 points to: {}", *r1);
// Modify through mutable pointer
*r2 = 100;
println!("r1 now points to: {}", *r1);
}
println!("value is now: {}", value);
}
fn main() {
unsafe_pointer_example();
}Key Points:
- Raw pointers (
*const Tand*mut T) don't enforce borrowing rules - Dereferencing requires
unsafeblock - Use only when necessary for performance or FFI
Example 14: FFI with C Functions
Scenario: Calling C library functions from Rust
use std::mem;
// External C function declarations
extern "C" {
fn abs(input: i32) -> i32;
}
// Rust function that can be called from C
#[no_mangle]
pub extern "C" fn rust_add(a: i32, b: i32) -> i32 {
a + b
}
// Example with function pointers
#[link(name = "foo")]
extern "C" {
fn do_twice(f: unsafe extern "C" fn(i32) -> i32, arg: i32) -> i32;
}
unsafe extern "C" fn add_one(x: i32) -> i32 {
x + 1
}
fn main() {
// Call C standard library function
let result = unsafe { abs(-42) };
println!("abs(-42) = {}", result);
// Call custom C function (if linked)
// let answer = unsafe { do_twice(add_one, 5) };
// println!("The answer is: {}", answer);
}Key Points:
extern "C"declares C-compatible functions#[no_mangle]prevents Rust name mangling for exports- All FFI calls must be in
unsafeblocks
Example 15: Implementing Unsafe Sync Trait
Scenario: Manual Sync implementation for types that aren't automatically Sync
use std::cell::Cell;
struct NotThreadSafe<T> {
value: Cell<T>,
}
// SAFETY: We must ensure all access to the Cell is properly synchronized
unsafe impl<T> Sync for NotThreadSafe<T> {}
static A: NotThreadSafe<usize> = NotThreadSafe {
value: Cell::new(1),
};
static B: &'static NotThreadSafe<usize> = &A;
fn main() {
println!("Value: {}", B.value.get());
B.value.set(42);
println!("Value: {}", B.value.get());
}Key Points:
unsafe implrequires manual safety guarantees- Document safety invariants in comments
- Only use when you fully understand the implications
---
Memory Safety and Sanitizers
Example 16: Detecting Buffer Overflows with AddressSanitizer
Scenario: Finding memory bugs with sanitizers
// Stack buffer overflow example
fn stack_overflow_example() {
let xs = [0, 1, 2, 3];
let _y = unsafe { *xs.as_ptr().offset(4) }; // Out of bounds!
}
// Heap buffer overflow example
fn heap_overflow_example() {
let xs = vec![0, 1, 2, 3];
let _y = unsafe { *xs.as_ptr().offset(4) }; // Out of bounds!
}
// Use-after-scope example
static mut P: *mut usize = std::ptr::null_mut();
fn use_after_scope_example() {
unsafe {
{
let mut x = 0;
P = &mut x;
}
// x is now out of scope
std::ptr::write_volatile(P, 123); // Use after scope!
}
}
fn main() {
// Uncomment to test each scenario
// stack_overflow_example();
// heap_overflow_example();
// use_after_scope_example();
}
// Build and run with AddressSanitizer:
// export RUSTFLAGS=-Zsanitizer=address RUSTDOCFLAGS=-Zsanitizer=address
// cargo run -Zbuild-std --target x86_64-unknown-linux-gnuKey Points:
- AddressSanitizer detects memory safety violations
- Enable with
RUSTFLAGS=-Zsanitizer=address - Provides detailed error reports with stack traces
Example 17: Detecting Data Races with ThreadSanitizer
Scenario: Finding race conditions in concurrent code
static mut A: usize = 0;
fn data_race_example() {
let t = std::thread::spawn(|| {
unsafe { A += 1 }; // Concurrent write
});
unsafe { A += 1 }; // Concurrent write
t.join().unwrap();
}
fn main() {
// Uncomment to trigger data race
// data_race_example();
}
// Build and run with ThreadSanitizer:
// export RUSTFLAGS=-Zsanitizer=thread RUSTDOCFLAGS=-Zsanitizer=thread
// cargo run -Zbuild-std --target x86_64-unknown-linux-gnuKey Points:
- ThreadSanitizer detects data races at runtime
- Enable with
RUSTFLAGS=-Zsanitizer=thread - Catches races that type system can't prevent in unsafe code
---
Performance Optimization
Example 18: Zero-Cost Abstractions and Iterators
Scenario: Comparing imperative vs functional approaches
fn imperative_sum(data: &[i32]) -> i32 {
let mut sum = 0;
for i in 0..data.len() {
if data[i] % 2 == 0 {
sum += data[i] * data[i];
}
}
sum
}
fn functional_sum(data: &[i32]) -> i32 {
data.iter()
.filter(|&&x| x % 2 == 0)
.map(|&x| x * x)
.sum()
}
fn main() {
let data: Vec<i32> = (0..1000).collect();
let start = std::time::Instant::now();
let result1 = imperative_sum(&data);
let elapsed1 = start.elapsed();
let start = std::time::Instant::now();
let result2 = functional_sum(&data);
let elapsed2 = start.elapsed();
println!("Imperative: {} ({:?})", result1, elapsed1);
println!("Functional: {} ({:?})", result2, elapsed2);
assert_eq!(result1, result2);
}Key Points:
- Iterator chains compile to efficient loops
- Zero runtime overhead for abstractions
- Functional style is both readable and performant
Example 19: Avoiding Allocations with String Slices
Scenario: Using &str instead of String when possible
// Bad: Allocates new String
fn greet_bad(name: &str) -> String {
format!("Hello, {}!", name)
}
// Good: Returns a borrowed reference
fn greet_good(name: &str) -> impl std::fmt::Display + '_ {
format_args!("Hello, {}!", name)
}
// Bad: Unnecessary String allocation
fn parse_header_bad(header: &str) -> (String, String) {
let parts: Vec<&str> = header.split(": ").collect();
(parts[0].to_string(), parts[1].to_string())
}
// Good: Return slices when possible
fn parse_header_good(header: &str) -> Option<(&str, &str)> {
let mut parts = header.split(": ");
let key = parts.next()?;
let value = parts.next()?;
Some((key, value))
}
fn main() {
// String allocation example
let name = "Alice";
println!("{}", greet_good(name));
// String slice parsing
let header = "Content-Type: application/json";
if let Some((key, value)) = parse_header_good(header) {
println!("{}: {}", key, value);
}
}Key Points:
- Prefer
&stroverStringfor read-only operations - Avoid
.to_string()unless ownership is needed - Slices prevent unnecessary allocations
Example 20: Inline and Monomorphization
Scenario: Understanding how generics and inlining work
#[inline]
fn add<T>(a: T, b: T) -> T
where
T: std::ops::Add<Output = T>,
{
a + b
}
#[inline(always)]
fn multiply<T>(a: T, b: T) -> T
where
T: std::ops::Mul<Output = T>,
{
a * b
}
fn main() {
// Monomorphized for i32
let x = add(5, 3);
println!("i32: {}", x);
// Monomorphized for f64
let y = add(5.0, 3.0);
println!("f64: {}", y);
// Inlined and monomorphized
let z = multiply(10, 20);
println!("Result: {}", z);
}Key Points:
- Generic functions are specialized for each concrete type
#[inline]suggests inlining to compiler#[inline(always)]forces inlining- Results in zero abstraction overhead
---
Production Web Server
Example 21: Async Web Server with Tokio
Scenario: Building a production-ready HTTP server
use tokio::net::{TcpListener, TcpStream};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use std::sync::Arc;
struct AppState {
request_count: std::sync::atomic::AtomicU64,
}
impl AppState {
fn new() -> Self {
AppState {
request_count: std::sync::atomic::AtomicU64::new(0),
}
}
fn increment_requests(&self) -> u64 {
self.request_count
.fetch_add(1, std::sync::atomic::Ordering::SeqCst)
}
}
async fn handle_client(mut stream: TcpStream, state: Arc<AppState>) {
let mut buffer = [0; 1024];
match stream.read(&mut buffer).await {
Ok(n) => {
if n == 0 {
return;
}
let request = String::from_utf8_lossy(&buffer[..n]);
println!("Received: {}", request);
let count = state.increment_requests();
let response = format!(
"HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\nRequest #{}\n",
format!("Request #{}\n", count).len(),
count
);
if let Err(e) = stream.write_all(response.as_bytes()).await {
eprintln!("Failed to write response: {}", e);
}
}
Err(e) => {
eprintln!("Failed to read from stream: {}", e);
}
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let listener = TcpListener::bind("127.0.0.1:8080").await?;
let state = Arc::new(AppState::new());
println!("Server listening on 127.0.0.1:8080");
loop {
let (stream, addr) = listener.accept().await?;
let state = Arc::clone(&state);
tokio::spawn(async move {
println!("New connection from: {}", addr);
handle_client(stream, state).await;
});
}
}Key Points:
- Async I/O enables handling many concurrent connections
- Shared state uses atomic operations for thread safety
- Each connection is handled in a separate task
---
CLI Application
Example 22: Command-Line Tool with Structured Output
Scenario: Building a professional CLI application
use std::env;
use std::process;
struct Config {
query: String,
filename: String,
case_sensitive: bool,
}
impl Config {
fn new(args: &[String]) -> Result<Config, &'static str> {
if args.len() < 3 {
return Err("Not enough arguments");
}
let query = args[1].clone();
let filename = args[2].clone();
let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
Ok(Config {
query,
filename,
case_sensitive,
})
}
}
fn search<'a>(query: &str, contents: &'a str, case_sensitive: bool) -> Vec<&'a str> {
contents
.lines()
.filter(|line| {
if case_sensitive {
line.contains(query)
} else {
line.to_lowercase().contains(&query.to_lowercase())
}
})
.collect()
}
fn run(config: Config) -> Result<(), Box<dyn std::error::Error>> {
let contents = std::fs::read_to_string(config.filename)?;
let results = search(&config.query, &contents, config.case_sensitive);
if results.is_empty() {
println!("No matches found");
} else {
println!("Found {} match(es):", results.len());
for line in results {
println!(" {}", line);
}
}
Ok(())
}
fn main() {
let args: Vec<String> = env::args().collect();
let config = Config::new(&args).unwrap_or_else(|err| {
eprintln!("Problem parsing arguments: {}", err);
eprintln!("Usage: {} <query> <filename>", args[0]);
process::exit(1);
});
if let Err(e) = run(config) {
eprintln!("Application error: {}", e);
process::exit(1);
}
}Key Points:
- Separate config parsing from business logic
- Use
Resultfor error handling - Provide helpful error messages
---
Database Connection Pool
Example 23: Production Database Pattern
Scenario: Managing database connections efficiently
use std::sync::Arc;
use tokio::sync::Semaphore;
struct DbConnection {
id: usize,
}
impl DbConnection {
fn new(id: usize) -> Self {
DbConnection { id }
}
async fn query(&self, sql: &str) -> Result<Vec<String>, String> {
println!("Connection {} executing: {}", self.id, sql);
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
Ok(vec![format!("Result from connection {}", self.id)])
}
}
struct ConnectionPool {
connections: Vec<Arc<DbConnection>>,
semaphore: Arc<Semaphore>,
}
impl ConnectionPool {
fn new(size: usize) -> Self {
let connections = (0..size)
.map(|i| Arc::new(DbConnection::new(i)))
.collect();
ConnectionPool {
connections,
semaphore: Arc::new(Semaphore::new(size)),
}
}
async fn get(&self) -> PooledConnection {
let permit = self.semaphore.clone().acquire_owned().await.unwrap();
let conn_idx = 0; // Simplified: would track which connection is free
PooledConnection {
connection: Arc::clone(&self.connections[conn_idx]),
_permit: permit,
}
}
}
struct PooledConnection {
connection: Arc<DbConnection>,
_permit: tokio::sync::OwnedSemaphorePermit,
}
impl std::ops::Deref for PooledConnection {
type Target = DbConnection;
fn deref(&self) -> &Self::Target {
&self.connection
}
}
#[tokio::main]
async fn main() {
let pool = Arc::new(ConnectionPool::new(5));
let mut handles = vec![];
for i in 0..20 {
let pool = Arc::clone(&pool);
let handle = tokio::spawn(async move {
let conn = pool.get().await;
let result = conn.query(&format!("SELECT * FROM users WHERE id = {}", i)).await;
println!("Query {} result: {:?}", i, result);
});
handles.push(handle);
}
for handle in handles {
handle.await.unwrap();
}
println!("All queries completed");
}Key Points:
- Connection pool limits concurrent database access
- Semaphore controls access to limited resources
- RAII pattern returns connections to pool automatically
---
Total Examples: 23 comprehensive, production-ready examples covering all major aspects of Rust systems programming from ownership to production patterns.
Rust Systems Programming
Build high-performance, memory-safe systems software with Rust's ownership model, zero-cost abstractions, and fearless concurrency.
Overview
Rust is a systems programming language that guarantees memory safety and thread safety at compile time, without requiring a garbage collector. It provides the performance of C/C++ with modern language features, making it ideal for systems software, embedded development, web services, and command-line tools.
The Rust Guarantee
Rust provides three fundamental guarantees:
1. Memory Safety Without Garbage Collection
Rust prevents common memory bugs at compile time:
- No null pointer dereferences: Use
Option<T>instead of null - No dangling pointers: References are always valid
- No buffer overflows: Bounds checking on arrays
- No use-after-free: Ownership system prevents accessing freed memory
- No data races: Thread safety enforced by the type system
2. Zero-Cost Abstractions
High-level features compile to efficient machine code:
- Iterators optimize to simple loops
- Generic functions are monomorphized (specialized per type)
- Pattern matching compiles to efficient jump tables
- Trait objects use virtual dispatch only when needed
- No runtime overhead for safety guarantees
3. Fearless Concurrency
The type system prevents data races:
Sendtrait: Types safe to transfer between threadsSynctrait: Types safe to share between threads- Compile-time enforcement prevents race conditions
- Lock APIs prevent deadlocks at type level
- Message passing with channels for safe communication
The Ownership Model
Rust's ownership system is the foundation of its guarantees:
// Each value has exactly one owner
let s1 = String::from("hello");
// Ownership can be transferred (moved)
let s2 = s1; // s1 is no longer valid
// Values can be borrowed immutably (any number of borrows)
let s3 = String::from("world");
let len = calculate_length(&s3);
// Or borrowed mutably (exactly one mutable borrow)
let mut s4 = String::from("foo");
change(&mut s4);
// When the owner goes out of scope, the value is droppedOwnership Rules: 1. Each value in Rust has exactly one owner 2. When the owner goes out of scope, the value is dropped 3. Values can be borrowed via references (&T or &mut T) 4. You can have either one mutable reference OR any number of immutable references 5. References must always be valid
Borrowing: The Key to Flexibility
Borrowing allows you to use values without taking ownership:
Immutable Borrowing
fn calculate_length(s: &String) -> usize {
s.len() // Can read, cannot modify
}
let s = String::from("hello");
let len = calculate_length(&s);
println!("{} has length {}", s, len); // s still validMutable Borrowing
fn append_world(s: &mut String) {
s.push_str(" world");
}
let mut s = String::from("hello");
append_world(&mut s);
println!("{}", s); // "hello world"Borrowing Rules Prevent Errors
let mut data = vec![1, 2, 3];
let first = &data[0]; // Immutable borrow
// data.push(4); // ERROR: Cannot mutate while borrowed
println!("{}", first); // Borrow ends here
data.push(4); // Now OKCore Types and Patterns
Smart Pointers
// Box: Heap allocation, single owner
let boxed = Box::new(5);
// Rc: Reference counted, single-threaded sharing
use std::rc::Rc;
let shared = Rc::new(5);
let cloned = Rc::clone(&shared);
// Arc: Atomic reference counted, thread-safe sharing
use std::sync::Arc;
let shared_thread_safe = Arc::new(5);
// RefCell: Interior mutability with runtime checks
use std::cell::RefCell;
let mutable = RefCell::new(5);
*mutable.borrow_mut() += 1;
// Mutex: Thread-safe interior mutability
use std::sync::Mutex;
let counter = Mutex::new(0);
*counter.lock().unwrap() += 1;Error Handling
Rust uses types for error handling, not exceptions:
// Result for recoverable errors
fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err("Division by zero".to_string())
} else {
Ok(a / b)
}
}
// Option for optional values
fn find_user(id: u64) -> Option<User> {
database.get(id)
}
// ? operator for error propagation
fn read_config(path: &str) -> Result<Config, Error> {
let contents = std::fs::read_to_string(path)?;
let config = parse_config(&contents)?;
Ok(config)
}Concurrency Patterns
Threads and Message Passing
use std::thread;
use std::sync::mpsc;
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let data = expensive_computation();
tx.send(data).unwrap();
});
let result = rx.recv().unwrap();
println!("Got result: {}", result);Shared State with Mutex
use std::sync::{Arc, Mutex};
use std::thread;
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());Async Programming
Rust's async/await enables efficient I/O without blocking threads:
use tokio;
#[tokio::main]
async fn main() {
let result = fetch_data().await;
println!("Data: {:?}", result);
}
async fn fetch_data() -> Result<String, Error> {
let response = reqwest::get("https://api.example.com/data").await?;
let text = response.text().await?;
Ok(text)
}
// Concurrent async operations
async fn fetch_multiple() -> Result<Vec<String>, Error> {
let (data1, data2, data3) = tokio::join!(
fetch_url("https://example.com/1"),
fetch_url("https://example.com/2"),
fetch_url("https://example.com/3"),
);
Ok(vec![data1?, data2?, data3?])
}When to Use Rust
Excellent For:
Systems Programming
- Operating systems and kernels
- Device drivers
- Embedded systems
- File systems and databases
High-Performance Applications
- Game engines
- Browser engines (Firefox uses Rust)
- Video/audio processing
- Real-time systems
Network Services
- Web servers and proxies
- Load balancers
- Network protocols
- API gateways
Command-Line Tools
- Fast, reliable CLI applications
- Cross-platform utilities
- Build tools and compilers
WebAssembly
- Near-native performance in browsers
- Portable binary format
- Safe sandboxed execution
Cryptography and Security
- Memory-safe crypto implementations
- Security-critical components
- Blockchain and distributed systems
Consider Alternatives For:
Rapid Prototyping: Python, JavaScript for faster iteration Data Science: Python's ecosystem is more mature Mobile Apps: Swift (iOS) or Kotlin (Android) for platform-specific features Enterprise Applications: Java or C# if you need existing enterprise ecosystems
Quick Start Guide
Installation
# Install Rust via rustup
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Update Rust
rustup update
# Check version
rustc --version
cargo --versionCreate a New Project
# Binary (application)
cargo new my_app
cd my_app
# Library
cargo new --lib my_libProject Structure
my_app/
├── Cargo.toml # Dependencies and metadata
├── Cargo.lock # Dependency lock file
└── src/
└── main.rs # Entry pointBuild and Run
# Build in debug mode
cargo build
# Build in release mode (optimized)
cargo build --release
# Run the application
cargo run
# Run tests
cargo test
# Check code without building
cargo check
# Format code
cargo fmt
# Lint code
cargo clippyAdding Dependencies
Edit Cargo.toml:
[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
reqwest = { version = "0.11", features = ["json"] }Then run:
cargo build # Downloads and compiles dependenciesCommon Patterns
The Builder Pattern
struct Server {
host: String,
port: u16,
workers: usize,
}
impl Server {
fn builder() -> ServerBuilder {
ServerBuilder::default()
}
}
#[derive(Default)]
struct ServerBuilder {
host: Option<String>,
port: Option<u16>,
workers: Option<usize>,
}
impl ServerBuilder {
fn host(mut self, host: impl Into<String>) -> Self {
self.host = Some(host.into());
self
}
fn port(mut self, port: u16) -> Self {
self.port = Some(port);
self
}
fn workers(mut self, workers: usize) -> Self {
self.workers = Some(workers);
self
}
fn build(self) -> Server {
Server {
host: self.host.unwrap_or_else(|| "127.0.0.1".to_string()),
port: self.port.unwrap_or(8080),
workers: self.workers.unwrap_or(4),
}
}
}
// Usage
let server = Server::builder()
.host("0.0.0.0")
.port(3000)
.workers(8)
.build();The Newtype Pattern
struct UserId(u64);
struct PostId(u64);
fn get_user(id: UserId) -> User { /* ... */ }
// Type safety: can't mix up IDs
// let user = get_user(PostId(42)); // Compile error!RAII (Resource Acquisition Is Initialization)
struct FileHandle {
file: std::fs::File,
}
impl FileHandle {
fn open(path: &str) -> std::io::Result<Self> {
Ok(FileHandle {
file: std::fs::File::create(path)?,
})
}
}
impl Drop for FileHandle {
fn drop(&mut self) {
println!("File automatically closed");
// File is closed when FileHandle goes out of scope
}
}Performance Tips
1. Use References: Avoid unnecessary clones by borrowing 2. Iterators Over Loops: Iterators often optimize better 3. Avoid Allocations: Use &str instead of String when possible 4. Profile First: Use cargo-flamegraph or perf to find bottlenecks 5. Enable LTO: Set lto = true in release profile 6. Use `cargo-bloat`: Find large dependencies 7. Benchmark: Use criterion for reliable benchmarks
Testing
// Unit tests in the same file
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_addition() {
assert_eq!(2 + 2, 4);
}
#[test]
#[should_panic(expected = "divide by zero")]
fn test_panic() {
divide(10, 0);
}
#[tokio::test]
async fn test_async() {
let result = async_function().await;
assert!(result.is_ok());
}
}
// Integration tests in tests/
// tests/integration_test.rs
use my_crate;
#[test]
fn test_public_api() {
assert_eq!(my_crate::add(2, 2), 4);
}
// Benchmarks in benches/
// benches/my_benchmark.rs
use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn fibonacci_benchmark(c: &mut Criterion) {
c.bench_function("fib 20", |b| {
b.iter(|| fibonacci(black_box(20)))
});
}
criterion_group!(benches, fibonacci_benchmark);
criterion_main!(benches);Documentation
/// Calculates the sum of two numbers.
///
/// # Examples
///
/// ```
/// let result = my_crate::add(2, 2);
/// assert_eq!(result, 4);
/// ```
///
/// # Panics
///
/// Panics if the result overflows.
///
/// # Errors
///
/// Returns `Err` if...
///
/// # Safety
///
/// Caller must ensure...
pub fn add(a: i32, b: i32) -> i32 {
a + b
}Generate documentation:
cargo doc --openCommunity Resources
Official Resources
- The Rust Book: https://doc.rust-lang.org/book/
- Rust by Example: https://doc.rust-lang.org/rust-by-example/
- Standard Library Docs: https://doc.rust-lang.org/std/
- Cargo Book: https://doc.rust-lang.org/cargo/
Advanced Resources
- The Rustonomicon: Unsafe Rust guide
- Async Book: Async programming in depth
- Performance Book: Optimization techniques
- Embedded Book: Embedded systems with Rust
Community
- Rust Users Forum: https://users.rust-lang.org/
- Rust Subreddit: r/rust
- Discord: Official Rust Discord server
- This Week in Rust: Weekly newsletter
Tools
- rustfmt: Code formatter
- clippy: Lint tool
- rust-analyzer: IDE language server
- cargo-edit: Manage dependencies from CLI
- cargo-watch: Auto-rebuild on file changes
Common Errors and Solutions
Borrow Checker Errors
"cannot borrow as mutable because it is also borrowed as immutable" → Ensure borrows don't overlap in scope
"cannot move out of borrowed content" → Use .clone(), take a reference, or use Rc<T>
"borrowed value does not live long enough" → Add lifetime annotations or restructure code
Compilation Errors
"trait X is not implemented for Y" → Derive or implement the trait, or use a different type
"type annotations needed" → Add explicit type annotations or help the compiler infer
"recursive type has infinite size" → Use Box<T> to create indirection
Next Steps
1. Complete The Rust Book: https://doc.rust-lang.org/book/ 2. Build Projects: Start with small CLI tools 3. Read Other's Code: Explore popular crates on crates.io 4. Join the Community: Ask questions, help others 5. Contribute: Fix bugs, improve documentation
---
Get Started: cargo new my_project && cd my_project && cargo run
Learn More: Explore SKILL.md and EXAMPLES.md for comprehensive patterns and production examples.