๐Ÿฆ€ Functional Rust
๐ŸŽฌ Traits & Generics Shared behaviour, static vs dynamic dispatch, zero-cost polymorphism.
๐Ÿ“ Text version (for readers / accessibility)

โ€ข Traits define shared behaviour โ€” like interfaces but with default implementations

โ€ข Generics with trait bounds: fn process(item: T) โ€” monomorphized at compile time

โ€ข Static dispatch (impl Trait) = zero cost; dynamic dispatch (dyn Trait) = runtime flexibility via vtable

โ€ข Blanket implementations apply traits to all types matching a bound

โ€ข Associated types and supertraits enable complex type relationships

340: Async Trait Pattern

Difficulty: 4 Level: Expert Async methods in traits require boxing โ€” `async fn` in traits isn't directly supported in stable Rust without the `async-trait` crate.

The Problem This Solves

You want to define a trait with async methods โ€” `trait Storage { async fn get(&self, key: &str) -> Option<String>; }` โ€” so you can swap implementations (in-memory, Redis, PostgreSQL) behind a common interface. But Rust traits can't directly express `async fn` in a stable, object-safe way: the return type of an async function is an opaque `impl Future`, and different implementations may return different future types. That makes the trait non-object-safe โ€” you can't use `dyn Storage`. The solution: return `Pin<Box<dyn Future<Output=Result<T,E>> + Send>>` explicitly. Each implementation boxes its async block. The `async-trait` crate (`#[async_trait]`) generates this boxing automatically, making the ergonomics identical to writing `async fn` in an impl block. This pattern is essential for writing testable, pluggable async services: mock stores in tests, real stores in production, both behind the same trait.

The Intuition

In TypeScript, you'd write:
interface Storage {
get(key: string): Promise<string | null>;
set(key: string, val: string): Promise<void>;
}
`Promise<T>` is always heap-allocated โ€” there's no equivalent to Rust's stack futures. Rust's `Pin<Box<dyn Future>>` is the explicit version of the same thing: heap-allocated, type-erased, sendable.

How It Works in Rust

// Type alias for readability
type AsyncResult<T, E> = Pin<Box<dyn Future<Output = Result<T, E>> + Send>>;

trait AsyncStore: Send + Sync {
 fn get(&self, key: &str) -> AsyncResult<Option<String>, String>;
 fn set(&self, key: String, val: String) -> AsyncResult<(), String>;
}

// In-memory implementation
impl AsyncStore for MemStore {
 fn get(&self, key: &str) -> AsyncResult<Option<String>, String> {
     let result = self.data.lock().unwrap().get(key).cloned();
     // Box::pin wraps the async block and pins it to the heap
     Box::pin(async move { Ok(result) })
 }
 fn set(&self, key: String, val: String) -> AsyncResult<(), String> {
     self.data.lock().unwrap().insert(key, val);
     Box::pin(async { Ok(()) })
 }
}

// Both can be used through the same trait object
let stores: Vec<Box<dyn AsyncStore>> = vec![
 Box::new(MemStore::new()),
 Box::new(FailStore),
];
With the `async-trait` crate, this simplifies to:
#[async_trait]
trait AsyncStore: Send + Sync {
 async fn get(&self, key: &str) -> Result<Option<String>, String>;
 async fn set(&self, key: String, val: String) -> Result<(), String>;
}
The crate generates the identical `Pin<Box<dyn Future>>` code behind the scenes.

What This Unlocks

Key Differences

ConceptOCamlRust
Async trait methodsModule type with `'a Lwt.t` returnExplicit `Pin<Box<dyn Future>>` or `#[async_trait]`
Dynamic dispatchFirst-class module / polymorphism`dyn Trait` (requires object-safe return types)
Trait object`(module Store)` pattern`Box<dyn AsyncStore>`
Boxing overheadAlways boxed (GC managed)Explicit `Box::pin(async { ... })` per call
use std::future::Future;
use std::pin::Pin;
use std::collections::HashMap;
use std::sync::Mutex;

type AsyncResult<T,E> = Pin<Box<dyn Future<Output=Result<T,E>>+Send>>;

trait AsyncStore: Send+Sync {
    fn get(&self, key: &str) -> AsyncResult<Option<String>, String>;
    fn set(&self, key: String, val: String) -> AsyncResult<(), String>;
}

struct MemStore { data: Mutex<HashMap<String,String>> }
impl MemStore { fn new() -> Self { Self{data:Mutex::new(HashMap::new())} } }

impl AsyncStore for MemStore {
    fn get(&self, key: &str) -> AsyncResult<Option<String>,String> {
        let r = self.data.lock().unwrap().get(key).cloned();
        Box::pin(async move { Ok(r) })
    }
    fn set(&self, key: String, val: String) -> AsyncResult<(),String> {
        self.data.lock().unwrap().insert(key, val);
        Box::pin(async { Ok(()) })
    }
}

struct FailStore;
impl AsyncStore for FailStore {
    fn get(&self, _: &str) -> AsyncResult<Option<String>,String> { Box::pin(async{Err("connection refused".into())}) }
    fn set(&self, _: String, _: String) -> AsyncResult<(),String> { Box::pin(async{Err("read-only".into())}) }
}

fn block_on<F:Future>(fut: F) -> F::Output {
    use std::task::{Context,Poll,RawWaker,RawWakerVTable,Waker};
    unsafe fn c(p:*const())->RawWaker{RawWaker::new(p,&V)} unsafe fn n(_:*const()){}
    static V:RawWakerVTable=RawWakerVTable::new(c,n,n,n);
    let w=unsafe{Waker::from_raw(RawWaker::new(std::ptr::null(),&V))};
    let mut cx=Context::from_waker(&w); let mut f=Box::pin(fut);
    loop{if let Poll::Ready(v)=f.as_mut().poll(&mut cx){return v;}}
}

fn use_store(s: &dyn AsyncStore, k: &str, v: &str) {
    match block_on(s.set(k.into(),v.into())) {
        Err(e) => println!("Set failed: {e}"),
        Ok(()) => match block_on(s.get(k)) {
            Ok(Some(v)) => println!("Got: {v}"),
            Ok(None) => println!("not found"),
            Err(e) => println!("Get failed: {e}"),
        }
    }
}

fn main() {
    use_store(&MemStore::new(), "k", "v");
    use_store(&FailStore, "k", "v");
    let stores: Vec<Box<dyn AsyncStore>> = vec![Box::new(MemStore::new()), Box::new(FailStore)];
    for (i,s) in stores.iter().enumerate() { print!("Store {i}: "); use_store(s.as_ref(),"key","val"); }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test] fn mem_get_set() {
        let s = MemStore::new();
        block_on(s.set("k".into(),"v".into())).unwrap();
        assert_eq!(block_on(s.get("k")).unwrap(), Some("v".into()));
    }
    #[test] fn failing_returns_err() {
        let s = FailStore;
        assert!(block_on(s.get("x")).is_err());
    }
}
(* OCaml: polymorphic async via module signature *)

module type STORE = sig
  val get : string -> (string option, string) result
  val set : string -> string -> (unit, string) result
end

module Memory : STORE = struct
  let db = Hashtbl.create 8
  let get k = Ok (Hashtbl.find_opt db k)
  let set k v = Hashtbl.replace db k v; Ok ()
end

module Failing : STORE = struct
  let get _ = Error "connection refused"
  let set _ _ = Error "read-only"
end

let use_store (module S:STORE) k v =
  match S.set k v with
  | Error e -> Printf.printf "Set failed: %s\n" e
  | Ok () -> match S.get k with
    | Ok (Some v) -> Printf.printf "Got: %s\n" v
    | Ok None -> print_endline "not found"
    | Error e -> Printf.printf "Get failed: %s\n" e

let () = use_store (module Memory) "k" "v"; use_store (module Failing) "k" "v"