๐Ÿฆ€ Functional Rust
๐ŸŽฌ Rust Ownership in 30 seconds Visual walkthrough of ownership, moves, and automatic memory management.
๐Ÿ“ Text version (for readers / accessibility)

โ€ข Each value in Rust has exactly one owner โ€” when the owner goes out of scope, the value is dropped

โ€ข Assignment moves ownership by default; the original binding becomes invalid

โ€ข Borrowing (&T / &mut T) lets you reference data without taking ownership

โ€ข The compiler enforces: many shared references OR one mutable reference, never both

โ€ข No garbage collector needed โ€” memory is freed deterministically at scope exit

312: Error Downcasting

Difficulty: 4 Level: Expert Recover the concrete error type from `Box<dyn Error>` at runtime using `.downcast_ref()` or `.downcast()`.

The Problem This Solves

`Box<dyn Error>` is the idiomatic "I don't know what error type this is" container. It's perfect for library boundaries, generic error handling, and collecting errors from different subsystems into a single `Vec<Box<dyn Error>>`. Type erasure makes all of this composable. But sometimes you're on the receiving end of that erased error and you do care about the specific type. Maybe you want to retry on `NetworkError::Timeout` but propagate `IoError::PermissionDenied`. With the concrete type erased, you're stuck treating all errors the same โ€” unless you downcast. Downcasting is Rust's controlled escape from the type system. It asks at runtime: "is this actually a `NetworkError` in disguise?" and gives you either a typed reference or `None`. This is explicit, opt-in dynamic typing โ€” safe because it returns `Option`/`Result` rather than panicking on type mismatch.

The Intuition

Think of `Box<dyn Error>` as a sealed envelope. Downcasting opens it and checks the label. If the label matches what you expected, you get the contents. If not, you get the envelope back unchanged (with `downcast`) or just `None` (with `downcast_ref`). No surprises, no undefined behavior.

How It Works in Rust

use std::error::Error;

// downcast_ref: borrow the concrete type (non-consuming)
fn handle(e: &dyn Error) {
 if let Some(net_err) = e.downcast_ref::<NetworkError>() {
     if net_err.code == 503 { retry(); return; }
 }
 if let Some(parse_err) = e.downcast_ref::<ParseError>() {
     eprintln!("Bad input: {}", parse_err.input);
     return;
 }
 eprintln!("Unhandled error: {}", e); // generic fallback
}

// downcast: consume the Box, get ownership or the Box back
let boxed: Box<dyn Error> = produce_error();
match boxed.downcast::<ParseError>() {
 Ok(pe) => println!("Got ParseError: {:?}", pe), // owned ParseError
 Err(original) => println!("Not a ParseError: {}", original),
}
The `TypeId` system powers downcasting under the hood. Each `'static` type has a unique ID; the cast checks IDs at runtime and is always safe.

What This Unlocks

Key Differences

ConceptOCamlRust
Dynamic error type`exn` (extensible exception type)`Box<dyn Error>`
Type inspectionPattern match on `exn` variants`downcast_ref::<T>()`
Failure modeMatch failure (unhandled exn)Returns `Option<&T>` (safe)
Ownership recoveryN/A (GC)`Box::downcast::<T>()` โ†’ `Result<Box<T>, Box<dyn Error>>`
Runtime costPattern match overheadSingle `TypeId` comparison
//! # Downcasting Boxed Errors
//!
//! `downcast_ref::<T>()` recovers the concrete type from `Box<dyn Error>`.

use std::error::Error;
use std::fmt;

#[derive(Debug, PartialEq)]
pub struct ParseError { pub input: String }

impl fmt::Display for ParseError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "parse error: '{}'", self.input)
    }
}
impl Error for ParseError {}

#[derive(Debug)]
pub struct NetworkError { pub code: u32, pub message: String }

impl fmt::Display for NetworkError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "network error {}: {}", self.code, self.message)
    }
}
impl Error for NetworkError {}

/// Handle error by downcasting to specific types
pub fn handle_error(e: &(dyn Error + 'static)) -> String {
    if let Some(pe) = e.downcast_ref::<ParseError>() {
        return format!("Parse error for: {}", pe.input);
    }
    if let Some(ne) = e.downcast_ref::<NetworkError>() {
        return format!("Network {}: {}", ne.code, ne.message);
    }
    format!("Unknown: {}", e)
}

/// Create heterogeneous error collection
pub fn make_errors() -> Vec<Box<dyn Error>> {
    vec![
        Box::new(ParseError { input: "abc".to_string() }),
        Box::new(NetworkError { code: 404, message: "not found".to_string() }),
    ]
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_downcast_ref_success() {
        let e: Box<dyn Error> = Box::new(ParseError { input: "x".to_string() });
        assert!(e.downcast_ref::<ParseError>().is_some());
        assert!(e.downcast_ref::<NetworkError>().is_none());
    }

    #[test]
    fn test_handle_parse_error() {
        let e: Box<dyn Error> = Box::new(ParseError { input: "test".to_string() });
        let result = handle_error(e.as_ref());
        assert!(result.contains("Parse error"));
    }

    #[test]
    fn test_handle_network_error() {
        let e: Box<dyn Error> = Box::new(NetworkError { code: 500, message: "fail".to_string() });
        let result = handle_error(e.as_ref());
        assert!(result.contains("Network 500"));
    }

    #[test]
    fn test_downcast_box() {
        let e: Box<dyn Error> = Box::new(ParseError { input: "abc".to_string() });
        let result = e.downcast::<ParseError>();
        assert!(result.is_ok());
    }
}
(* 312. Downcasting boxed errors - OCaml *)
(* OCaml: match on exception type directly *)

exception ParseError of string
exception IoError of string
exception NetworkError of int * string

let process_error exn =
  match exn with
  | ParseError msg -> Printf.printf "Parse: %s\n" msg
  | IoError path -> Printf.printf "IO: %s\n" path
  | NetworkError (code, msg) -> Printf.printf "Network %d: %s\n" code msg
  | _ -> Printf.printf "Unknown: %s\n" (Printexc.to_string exn)

let () =
  let errors = [
    ParseError "invalid number";
    IoError "/etc/missing";
    NetworkError (404, "not found");
  ] in
  List.iter process_error errors