Agent skill
rust-patterns
Rust design patterns and advanced language patterns. Use when designing APIs, implementing complex abstractions, applying type-level programming, or looking for idiomatic solutions to common problems.
Stars
163
Forks
31
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/development/rust-patterns
SKILL.md
Rust Design Patterns and Advanced Patterns
Comprehensive guide to idiomatic Rust patterns and advanced techniques.
Creational Patterns
Builder Pattern
rust
use std::time::Duration;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum BuildError {
#[error("missing required field: {0}")]
MissingField(&'static str),
}
#[derive(Debug)]
pub struct Server {
host: String,
port: u16,
timeout: Duration,
max_connections: usize,
}
#[derive(Default)]
pub struct ServerBuilder {
host: Option<String>,
port: Option<u16>,
timeout: Option<Duration>,
max_connections: Option<usize>,
}
impl ServerBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn host(mut self, host: impl Into<String>) -> Self {
self.host = Some(host.into());
self
}
pub fn port(mut self, port: u16) -> Self {
self.port = Some(port);
self
}
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self
}
pub fn max_connections(mut self, max: usize) -> Self {
self.max_connections = Some(max);
self
}
pub fn build(self) -> Result<Server, BuildError> {
Ok(Server {
host: self.host.ok_or(BuildError::MissingField("host"))?,
port: self.port.unwrap_or(8080),
timeout: self.timeout.unwrap_or(Duration::from_secs(30)),
max_connections: self.max_connections.unwrap_or(100),
})
}
}
impl Server {
pub fn builder() -> ServerBuilder {
ServerBuilder::new()
}
}
// Usage
let server = Server::builder()
.host("localhost")
.port(3000)
.timeout(Duration::from_secs(60))
.build()?;
Typestate Builder (Compile-Time Validation)
rust
use std::marker::PhantomData;
// States
pub struct NoHost;
pub struct HasHost;
pub struct NoPort;
pub struct HasPort;
pub struct ServerBuilder<H, P> {
host: Option<String>,
port: Option<u16>,
_state: PhantomData<(H, P)>,
}
impl ServerBuilder<NoHost, NoPort> {
pub fn new() -> Self {
Self {
host: None,
port: None,
_state: PhantomData,
}
}
}
impl<P> ServerBuilder<NoHost, P> {
pub fn host(self, host: impl Into<String>) -> ServerBuilder<HasHost, P> {
ServerBuilder {
host: Some(host.into()),
port: self.port,
_state: PhantomData,
}
}
}
impl<H> ServerBuilder<H, NoPort> {
pub fn port(self, port: u16) -> ServerBuilder<H, HasPort> {
ServerBuilder {
host: self.host,
port: Some(port),
_state: PhantomData,
}
}
}
// Only available when both are set
impl ServerBuilder<HasHost, HasPort> {
pub fn build(self) -> Server {
Server {
host: self.host.unwrap(),
port: self.port.unwrap(),
}
}
}
// Compile-time error if host or port not set
let server = ServerBuilder::new()
.host("localhost")
.port(8080)
.build(); // OK
// let server = ServerBuilder::new()
// .host("localhost")
// .build(); // Compile error: no method `build` for HasHost, NoPort
Factory Pattern
rust
pub trait Transport: Send + Sync {
fn send(&self, data: &[u8]) -> Result<(), Error>;
}
pub struct TcpTransport { /* ... */ }
pub struct UdpTransport { /* ... */ }
pub struct UnixTransport { /* ... */ }
impl Transport for TcpTransport { /* ... */ }
impl Transport for UdpTransport { /* ... */ }
impl Transport for UnixTransport { /* ... */ }
pub fn create_transport(uri: &str) -> Result<Box<dyn Transport>, Error> {
let scheme = uri.split("://").next().ok_or(Error::InvalidUri)?;
match scheme {
"tcp" => Ok(Box::new(TcpTransport::connect(uri)?)),
"udp" => Ok(Box::new(UdpTransport::bind(uri)?)),
"unix" => Ok(Box::new(UnixTransport::connect(uri)?)),
_ => Err(Error::UnsupportedScheme(scheme.into())),
}
}
Structural Patterns
Newtype Pattern
rust
// Wrap primitives for type safety
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct UserId(u64);
impl UserId {
pub fn new(id: u64) -> Self {
Self(id)
}
pub fn as_u64(&self) -> u64 {
self.0
}
}
// Validated newtype
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Email(String);
impl Email {
pub fn new(email: impl Into<String>) -> Result<Self, ValidationError> {
let email = email.into();
if email.contains('@') && email.len() <= 254 {
Ok(Self(email))
} else {
Err(ValidationError::InvalidEmail)
}
}
pub fn as_str(&self) -> &str {
&self.0
}
}
// Can't create invalid Email
// let email = Email("invalid".into()); // No direct construction
let email = Email::new("user@example.com")?; // Must validate
Type Aliases for Clarity
rust
// Simple alias
pub type Result<T> = std::result::Result<T, Error>;
// Generic alias with constraints
pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
// Associated type pattern
pub trait Container {
type Item;
type Error;
fn get(&self, key: &str) -> Result<Self::Item, Self::Error>;
}
Extension Traits
rust
// Extend types you don't own
pub trait StringExt {
fn truncate_ellipsis(&self, max_len: usize) -> String;
}
impl StringExt for str {
fn truncate_ellipsis(&self, max_len: usize) -> String {
if self.len() <= max_len {
self.to_string()
} else {
format!("{}...", &self[..max_len.saturating_sub(3)])
}
}
}
// Extend external types with your traits
pub trait ResultExt<T, E> {
fn log_err(self) -> Self;
}
impl<T, E: std::fmt::Display> ResultExt<T, E> for Result<T, E> {
fn log_err(self) -> Self {
if let Err(ref e) = self {
tracing::error!(error = %e, "Operation failed");
}
self
}
}
// Usage
let result = fallible_operation().log_err()?;
Deref for Smart Pointers
rust
use std::ops::{Deref, DerefMut};
pub struct Wrapper<T> {
inner: T,
metadata: Metadata,
}
impl<T> Deref for Wrapper<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<T> DerefMut for Wrapper<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
// Methods on T are available on Wrapper<T>
let wrapper = Wrapper { inner: String::from("hello"), metadata: Metadata::new() };
println!("{}", wrapper.len()); // String::len() via Deref
Behavioral Patterns
Strategy Pattern with Traits
rust
pub trait CompressionStrategy: Send + Sync {
fn compress(&self, data: &[u8]) -> Result<Vec<u8>, Error>;
fn decompress(&self, data: &[u8]) -> Result<Vec<u8>, Error>;
}
pub struct GzipStrategy;
pub struct ZstdStrategy { level: i32 }
pub struct NoCompression;
impl CompressionStrategy for GzipStrategy {
fn compress(&self, data: &[u8]) -> Result<Vec<u8>, Error> { /* ... */ }
fn decompress(&self, data: &[u8]) -> Result<Vec<u8>, Error> { /* ... */ }
}
impl CompressionStrategy for ZstdStrategy {
fn compress(&self, data: &[u8]) -> Result<Vec<u8>, Error> { /* ... */ }
fn decompress(&self, data: &[u8]) -> Result<Vec<u8>, Error> { /* ... */ }
}
pub struct Storage<C: CompressionStrategy> {
compression: C,
}
impl<C: CompressionStrategy> Storage<C> {
pub fn new(compression: C) -> Self {
Self { compression }
}
pub fn store(&self, data: &[u8]) -> Result<(), Error> {
let compressed = self.compression.compress(data)?;
// Store compressed data
Ok(())
}
}
Command Pattern
rust
pub trait Command: Send {
fn execute(&self) -> Result<(), Error>;
fn undo(&self) -> Result<(), Error>;
}
pub struct CreateFileCommand {
path: PathBuf,
}
impl Command for CreateFileCommand {
fn execute(&self) -> Result<(), Error> {
std::fs::File::create(&self.path)?;
Ok(())
}
fn undo(&self) -> Result<(), Error> {
std::fs::remove_file(&self.path)?;
Ok(())
}
}
pub struct CommandHistory {
executed: Vec<Box<dyn Command>>,
}
impl CommandHistory {
pub fn execute(&mut self, cmd: Box<dyn Command>) -> Result<(), Error> {
cmd.execute()?;
self.executed.push(cmd);
Ok(())
}
pub fn undo_last(&mut self) -> Result<(), Error> {
if let Some(cmd) = self.executed.pop() {
cmd.undo()?;
}
Ok(())
}
}
Visitor Pattern
rust
pub trait Visitor {
fn visit_file(&mut self, file: &File);
fn visit_directory(&mut self, dir: &Directory);
}
pub trait Visitable {
fn accept(&self, visitor: &mut dyn Visitor);
}
pub struct File {
pub name: String,
pub size: u64,
}
pub struct Directory {
pub name: String,
pub children: Vec<Box<dyn Visitable>>,
}
impl Visitable for File {
fn accept(&self, visitor: &mut dyn Visitor) {
visitor.visit_file(self);
}
}
impl Visitable for Directory {
fn accept(&self, visitor: &mut dyn Visitor) {
visitor.visit_directory(self);
for child in &self.children {
child.accept(visitor);
}
}
}
// Size calculator visitor
pub struct SizeCalculator {
pub total_size: u64,
}
impl Visitor for SizeCalculator {
fn visit_file(&mut self, file: &File) {
self.total_size += file.size;
}
fn visit_directory(&mut self, _dir: &Directory) {
// Directories don't have size themselves
}
}
Advanced Type Patterns
Phantom Types
rust
use std::marker::PhantomData;
// Marker types for state
pub struct Locked;
pub struct Unlocked;
pub struct Door<State> {
_state: PhantomData<State>,
}
impl Door<Locked> {
pub fn unlock(self) -> Door<Unlocked> {
Door { _state: PhantomData }
}
}
impl Door<Unlocked> {
pub fn lock(self) -> Door<Locked> {
Door { _state: PhantomData }
}
pub fn open(&self) {
println!("Opening door");
}
}
// Can only open unlocked doors
let door: Door<Locked> = Door { _state: PhantomData };
// door.open(); // Compile error: no method `open` for Door<Locked>
let door = door.unlock();
door.open(); // OK
Type-Level State Machines
rust
use std::marker::PhantomData;
// HTTP request states
pub struct Created;
pub struct HeadersSet;
pub struct BodySet;
pub struct Sent;
pub struct Request<State> {
url: String,
headers: Vec<(String, String)>,
body: Option<Vec<u8>>,
_state: PhantomData<State>,
}
impl Request<Created> {
pub fn new(url: impl Into<String>) -> Self {
Self {
url: url.into(),
headers: Vec::new(),
body: None,
_state: PhantomData,
}
}
pub fn header(mut self, name: &str, value: &str) -> Request<HeadersSet> {
self.headers.push((name.into(), value.into()));
Request {
url: self.url,
headers: self.headers,
body: self.body,
_state: PhantomData,
}
}
}
impl Request<HeadersSet> {
pub fn header(mut self, name: &str, value: &str) -> Self {
self.headers.push((name.into(), value.into()));
self
}
pub fn body(self, body: Vec<u8>) -> Request<BodySet> {
Request {
url: self.url,
headers: self.headers,
body: Some(body),
_state: PhantomData,
}
}
pub fn send(self) -> Result<Response, Error> {
// Send request without body
todo!()
}
}
impl Request<BodySet> {
pub fn send(self) -> Result<Response, Error> {
// Send request with body
todo!()
}
}
Sealed Traits
rust
mod private {
pub trait Sealed {}
}
/// A trait that cannot be implemented outside this crate.
pub trait DatabaseDriver: private::Sealed {
fn connect(&self, url: &str) -> Result<Connection, Error>;
}
pub struct PostgresDriver;
pub struct SqliteDriver;
impl private::Sealed for PostgresDriver {}
impl private::Sealed for SqliteDriver {}
impl DatabaseDriver for PostgresDriver {
fn connect(&self, url: &str) -> Result<Connection, Error> { /* ... */ }
}
impl DatabaseDriver for SqliteDriver {
fn connect(&self, url: &str) -> Result<Connection, Error> { /* ... */ }
}
// External crates cannot implement DatabaseDriver
GATs (Generic Associated Types)
rust
pub trait StreamingIterator {
type Item<'a> where Self: 'a;
fn next(&mut self) -> Option<Self::Item<'_>>;
}
pub struct WindowedSlice<'data, T> {
data: &'data [T],
pos: usize,
window_size: usize,
}
impl<'data, T> StreamingIterator for WindowedSlice<'data, T> {
type Item<'a> = &'a [T] where Self: 'a;
fn next(&mut self) -> Option<Self::Item<'_>> {
if self.pos + self.window_size <= self.data.len() {
let window = &self.data[self.pos..self.pos + self.window_size];
self.pos += 1;
Some(window)
} else {
None
}
}
}
HRTB (Higher-Rank Trait Bounds)
rust
// Function that accepts a closure working with any lifetime
fn apply_to_ref<F>(f: F)
where
F: for<'a> Fn(&'a str) -> &'a str,
{
let s = String::from("hello");
println!("{}", f(&s));
}
// Useful for callbacks that work with borrowed data
fn process_with_callback<F>(data: Vec<String>, callback: F)
where
F: for<'a> Fn(&'a str) -> bool,
{
for item in &data {
if callback(item) {
println!("Match: {}", item);
}
}
}
// Usage
apply_to_ref(|s| s);
process_with_callback(data, |s| s.starts_with("prefix"));
Variance and Lifetime Bounds
rust
use std::marker::PhantomData;
// Covariant over 'a - can shorten lifetime
struct Covariant<'a, T> {
value: &'a T,
}
// Invariant over 'a - lifetime must match exactly
struct Invariant<'a, T> {
value: &'a mut T,
}
// Contravariant (rare) - can lengthen lifetime
struct Contravariant<'a, T> {
func: fn(&'a T),
_marker: PhantomData<T>,
}
// Lifetime bounds on generic types
struct Cache<'a, T: 'a> {
data: &'a T,
}
fn store_reference<'a, T: 'a>(cache: &mut Cache<'a, T>, value: &'a T) {
cache.data = value;
}
Never Type and Diverging Functions
rust
// The never type (!) indicates a function never returns
fn diverges() -> ! {
panic!("This never returns!");
}
// Useful in match arms
fn example(condition: bool) -> i32 {
if condition {
42
} else {
diverges() // ! coerces to any type
}
}
// Common in infinite loops
fn run_server() -> ! {
loop {
accept_connection();
}
}
// In Result handling
fn must_succeed() -> Value {
match fallible_op() {
Ok(v) => v,
Err(_) => std::process::exit(1), // returns !
}
}
DST and ?Sized
rust
// [T] and str are Dynamically Sized Types (DST)
fn print_slice<T: std::fmt::Debug>(slice: &[T]) {
println!("{:?}", slice);
}
// By default, generics require T: Sized
// Use ?Sized to accept unsized types
fn generic_unsized<T: ?Sized + std::fmt::Debug>(value: &T) {
println!("{:?}", value);
}
// Works with both sized and unsized types
generic_unsized(&42i32); // &i32 - sized
generic_unsized("hello"); // &str - unsized
generic_unsized(&[1, 2, 3]); // &[i32] - can work too
// Trait objects are also DST
fn call_draw(drawable: &dyn Draw) {
drawable.draw();
}
Zero-Sized Types (ZST)
rust
struct MyZst;
// Size = 0, no memory allocation needed
assert_eq!(std::mem::size_of::<MyZst>(), 0);
// Useful for type-level markers
struct Collection<T, Strategy = DefaultStrategy> {
items: Vec<T>,
_strategy: PhantomData<Strategy>,
}
struct DefaultStrategy;
struct SortedStrategy;
// The strategy marker has no runtime cost
impl<T> Collection<T, DefaultStrategy> {
fn add(&mut self, item: T) {
self.items.push(item);
}
}
impl<T: Ord> Collection<T, SortedStrategy> {
fn add(&mut self, item: T) {
let pos = self.items.binary_search(&item).unwrap_or_else(|p| p);
self.items.insert(pos, item);
}
}
Error Handling Patterns
Error Enums with Context
rust
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ServiceError {
#[error("user not found: {user_id}")]
UserNotFound { user_id: u64 },
#[error("permission denied for {action} on {resource}")]
PermissionDenied { action: String, resource: String },
#[error("rate limit exceeded: {limit} requests per {window:?}")]
RateLimited { limit: u32, window: Duration },
#[error("database error")]
Database(#[from] sqlx::Error),
#[error("external service error: {service}")]
ExternalService {
service: String,
#[source]
source: reqwest::Error,
},
}
impl ServiceError {
pub fn is_retryable(&self) -> bool {
matches!(self,
ServiceError::RateLimited { .. } |
ServiceError::Database(_) |
ServiceError::ExternalService { .. }
)
}
pub fn status_code(&self) -> u16 {
match self {
ServiceError::UserNotFound { .. } => 404,
ServiceError::PermissionDenied { .. } => 403,
ServiceError::RateLimited { .. } => 429,
ServiceError::Database(_) => 500,
ServiceError::ExternalService { .. } => 502,
}
}
}
Result Extensions
rust
pub trait ResultExt<T, E> {
fn inspect_ok<F: FnOnce(&T)>(self, f: F) -> Self;
fn inspect_err_ref<F: FnOnce(&E)>(self, f: F) -> Self;
fn with_context<C, F>(self, f: F) -> Result<T, ContextError<E, C>>
where
F: FnOnce() -> C;
}
impl<T, E> ResultExt<T, E> for Result<T, E> {
fn inspect_ok<F: FnOnce(&T)>(self, f: F) -> Self {
if let Ok(ref value) = self {
f(value);
}
self
}
fn inspect_err_ref<F: FnOnce(&E)>(self, f: F) -> Self {
if let Err(ref e) = self {
f(e);
}
self
}
fn with_context<C, F>(self, f: F) -> Result<T, ContextError<E, C>>
where
F: FnOnce() -> C,
{
self.map_err(|e| ContextError {
context: f(),
source: e,
})
}
}
Async Patterns
Async Trait Methods
rust
use async_trait::async_trait;
#[async_trait]
pub trait Repository: Send + Sync {
type Entity;
type Error;
async fn find(&self, id: &str) -> Result<Option<Self::Entity>, Self::Error>;
async fn save(&self, entity: &Self::Entity) -> Result<(), Self::Error>;
async fn delete(&self, id: &str) -> Result<bool, Self::Error>;
}
#[async_trait]
impl Repository for UserRepository {
type Entity = User;
type Error = DbError;
async fn find(&self, id: &str) -> Result<Option<User>, DbError> {
// ...
}
async fn save(&self, entity: &User) -> Result<(), DbError> {
// ...
}
async fn delete(&self, id: &str) -> Result<bool, DbError> {
// ...
}
}
Cancellation Token Pattern
rust
use tokio_util::sync::CancellationToken;
pub struct Worker {
cancel: CancellationToken,
}
impl Worker {
pub fn new() -> Self {
Self {
cancel: CancellationToken::new(),
}
}
pub async fn run(&self) {
loop {
tokio::select! {
_ = self.cancel.cancelled() => {
tracing::info!("Worker cancelled");
break;
}
_ = self.do_work() => {
// Work completed, continue
}
}
}
}
pub fn stop(&self) {
self.cancel.cancel();
}
async fn do_work(&self) {
// ...
}
}
Retry with Backoff
rust
use std::time::Duration;
use tokio::time::sleep;
pub struct RetryConfig {
pub max_attempts: u32,
pub initial_delay: Duration,
pub max_delay: Duration,
pub multiplier: f64,
}
impl Default for RetryConfig {
fn default() -> Self {
Self {
max_attempts: 3,
initial_delay: Duration::from_millis(100),
max_delay: Duration::from_secs(10),
multiplier: 2.0,
}
}
}
pub async fn retry<T, E, F, Fut>(
config: &RetryConfig,
mut operation: F,
) -> Result<T, E>
where
F: FnMut() -> Fut,
Fut: std::future::Future<Output = Result<T, E>>,
E: std::fmt::Display,
{
let mut delay = config.initial_delay;
let mut attempts = 0;
loop {
attempts += 1;
match operation().await {
Ok(result) => return Ok(result),
Err(e) if attempts >= config.max_attempts => {
tracing::error!(attempts, error = %e, "All retry attempts failed");
return Err(e);
}
Err(e) => {
tracing::warn!(attempts, error = %e, "Attempt failed, retrying");
sleep(delay).await;
delay = Duration::from_secs_f64(
(delay.as_secs_f64() * config.multiplier).min(config.max_delay.as_secs_f64())
);
}
}
}
}
Resource Management
RAII Guards
rust
pub struct FileGuard {
path: PathBuf,
}
impl FileGuard {
pub fn create(path: impl Into<PathBuf>) -> std::io::Result<Self> {
let path = path.into();
std::fs::File::create(&path)?;
Ok(Self { path })
}
}
impl Drop for FileGuard {
fn drop(&mut self) {
let _ = std::fs::remove_file(&self.path);
}
}
// File is automatically deleted when guard goes out of scope
fn use_temp_file() -> Result<(), Error> {
let guard = FileGuard::create("/tmp/temp_file")?;
// Do work with file
// File deleted here when guard drops
Ok(())
}
Scoped Resources
rust
pub fn with_connection<F, R>(url: &str, f: F) -> Result<R, Error>
where
F: FnOnce(&mut Connection) -> Result<R, Error>,
{
let mut conn = Connection::connect(url)?;
let result = f(&mut conn);
conn.close()?;
result
}
// Usage
let result = with_connection("postgres://...", |conn| {
conn.execute("SELECT * FROM users")?;
Ok(())
})?;
Object Pool
rust
use std::sync::{Arc, Mutex};
use std::collections::VecDeque;
pub struct Pool<T> {
available: Mutex<VecDeque<T>>,
create: Box<dyn Fn() -> T + Send + Sync>,
max_size: usize,
}
pub struct PoolGuard<'a, T> {
pool: &'a Pool<T>,
item: Option<T>,
}
impl<T> Pool<T> {
pub fn new<F>(max_size: usize, create: F) -> Self
where
F: Fn() -> T + Send + Sync + 'static,
{
Self {
available: Mutex::new(VecDeque::new()),
create: Box::new(create),
max_size,
}
}
pub fn get(&self) -> PoolGuard<'_, T> {
let item = self.available.lock().unwrap().pop_front()
.unwrap_or_else(|| (self.create)());
PoolGuard {
pool: self,
item: Some(item),
}
}
fn return_item(&self, item: T) {
let mut available = self.available.lock().unwrap();
if available.len() < self.max_size {
available.push_back(item);
}
}
}
impl<'a, T> std::ops::Deref for PoolGuard<'a, T> {
type Target = T;
fn deref(&self) -> &T {
self.item.as_ref().unwrap()
}
}
impl<'a, T> Drop for PoolGuard<'a, T> {
fn drop(&mut self) {
if let Some(item) = self.item.take() {
self.pool.return_item(item);
}
}
}
Functional Patterns
Railway-Oriented Programming
rust
pub fn process_user(id: &str) -> Result<User, Error> {
fetch_user(id)
.and_then(validate_user)
.and_then(enrich_user)
.and_then(save_user)
}
// With early returns for clarity
pub fn process_user_explicit(id: &str) -> Result<User, Error> {
let user = fetch_user(id)?;
let user = validate_user(user)?;
let user = enrich_user(user)?;
save_user(user)
}
Monad-like Chaining
rust
pub struct Pipeline<T> {
value: T,
}
impl<T> Pipeline<T> {
pub fn new(value: T) -> Self {
Self { value }
}
pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Pipeline<U> {
Pipeline { value: f(self.value) }
}
pub fn and_then<U, F: FnOnce(T) -> Pipeline<U>>(self, f: F) -> Pipeline<U> {
f(self.value)
}
pub fn tap<F: FnOnce(&T)>(self, f: F) -> Self {
f(&self.value);
self
}
pub fn finish(self) -> T {
self.value
}
}
// Usage
let result = Pipeline::new(input)
.map(parse)
.tap(|x| tracing::debug!("Parsed: {:?}", x))
.map(validate)
.map(transform)
.finish();
Compose Functions
rust
pub fn compose<A, B, C, F, G>(f: F, g: G) -> impl Fn(A) -> C
where
F: Fn(A) -> B,
G: Fn(B) -> C,
{
move |x| g(f(x))
}
// Usage
let parse_and_validate = compose(parse, validate);
let result = parse_and_validate(input);
Pattern Matching Patterns
Match Guards with Let Chains (Rust 2024)
rust
fn process(opt: Option<Value>) -> Result<(), Error> {
match opt {
Some(v) if v.is_valid() && let Ok(data) = v.parse() => {
handle_valid(data)
}
Some(v) if v.is_recoverable() => {
handle_recovery(v)
}
Some(_) => Err(Error::Invalid),
None => Err(Error::Missing),
}
}
Destructuring Patterns
rust
struct Point { x: i32, y: i32, z: i32 }
fn process_point(p: Point) {
match p {
// Exact match
Point { x: 0, y: 0, z: 0 } => println!("Origin"),
// Partial match with binding
Point { x, y: 0, .. } => println!("On X axis at {}", x),
// Range patterns
Point { x: 0..=10, y, z } => println!("Near origin: y={}, z={}", y, z),
// Or patterns
Point { x: 0, .. } | Point { y: 0, .. } => println!("On an axis"),
// Binding with @
Point { x: x @ 100.., y, z } => println!("Far point x={}", x),
// Catch-all
p => println!("Point at ({}, {}, {})", p.x, p.y, p.z),
}
}
Slice Patterns
rust
fn analyze_slice(slice: &[i32]) {
match slice {
[] => println!("Empty"),
[single] => println!("Single element: {}", single),
[first, second] => println!("Pair: {}, {}", first, second),
[first, .., last] => println!("First: {}, Last: {}", first, last),
[first, middle @ .., last] => {
println!("First: {}, Middle: {:?}, Last: {}", first, middle, last)
}
}
}
fn starts_with(slice: &[u8], prefix: &[u8]) -> bool {
match (slice, prefix) {
(_, []) => true,
([x, xs @ ..], [y, ys @ ..]) if x == y => starts_with(xs, ys),
_ => false,
}
}
Didn't find tool you were looking for?