๐Ÿฆ€ 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

081: Newtype Pattern

Difficulty: 2 Level: Intermediate Wrap a primitive in a single-field tuple struct to create a distinct type the compiler enforces โ€” preventing you from accidentally mixing `UserId` with `OrderId` even though both are `u64`.

The Problem This Solves

Functions that take multiple `u64` parameters are accidents waiting to happen. `process_order(user_id, order_id)` compiles equally well as `process_order(order_id, user_id)` โ€” you only discover the bug at runtime, or not at all. The same problem exists with units. Adding Celsius temperatures to Fahrenheit is a type error that only shows up when your code produces obviously wrong results. The famous Mars Climate Orbiter was lost because of a Newtons vs pound-force mix-up โ€” a bug that a type system would have caught. Rust's newtype pattern uses a one-field tuple struct: `struct UserId(u64)`. The inner value is still a `u64`, but `UserId` and `OrderId` are different types at compile time. You must explicitly extract the inner value โ€” accidental misuse becomes a compile error.

The Intuition

In Python or JavaScript, you'd add a comment: `# this is a user id, not an order id`. In Haskell and OCaml, newtypes are a standard tool. In Java, you'd write a thin wrapper class (verbose but effective). In Rust, a tuple struct is zero-cost: `struct UserId(u64)` compiles down to exactly the same memory layout as a bare `u64` โ€” no overhead, full type safety. The key insight: the newtype doesn't just rename the type. It creates a new type that is not interchangeable with the original. You can't accidentally pass a `UserId` where an `OrderId` is expected.

How It Works in Rust

// Zero-cost wrappers โ€” same memory layout as u64
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct UserId(u64);

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct OrderId(u64);

// Compiler now prevents mixing these up โ€” this is a TYPE ERROR:
// process_order(user_id, order_id) โ† args are in the right positions
// process_order(order_id, user_id) โ† compile error: expected UserId, found OrderId
fn process_order(order: OrderId, user: UserId) -> String {
 format!("{:?} placed {:?}", user, order)
}
// Validation at construction time โ€” invalid values can never exist
struct Email(String);

impl Email {
 fn new(s: &str) -> Option<Self> {
     if s.contains('@') { Some(Email(s.to_string())) } else { None }
 }
}
// Once you have an Email, you know it was validated
// Units โ€” Celsius and Fahrenheit can't be accidentally added
struct Celsius(f64);
struct Fahrenheit(f64);

impl Celsius {
 fn to_fahrenheit(self) -> Fahrenheit {
     Fahrenheit(self.0 * 9.0 / 5.0 + 32.0)
 }
}
// let temp: Celsius = body_temp + room_temp   โ† type error if units differ
// Deref for transparent access when appropriate
impl Deref for NonEmptyString {
 type Target = str;
 fn deref(&self) -> &str { &self.0 }
}
// Now all &str methods work on NonEmptyString automatically

What This Unlocks

Key Differences

ConceptOCamlRust
Newtype definition`type user_id = UserId of int``struct UserId(u64)`
Inner value accessPattern match: `let UserId n = id``id.0` or custom accessor
Runtime costZero (optimized away)Zero (same memory layout)
Auto-derive traitsMust implement manually`#[derive(Debug, Clone, Copy, ...)]`
Transparent accessAutomatic coercion sometimesExplicit `Deref` impl
// Example 081: Newtype Pattern
// Rust tuple structs for type safety

use std::fmt;

// === Approach 1: Simple newtype wrappers ===
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct UserId(u64);

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct OrderId(u64);

impl UserId {
    fn new(id: u64) -> Option<Self> {
        if id > 0 { Some(UserId(id)) } else { None }
    }
    fn value(self) -> u64 { self.0 }
}

impl OrderId {
    fn new(id: u64) -> Option<Self> {
        if id > 0 { Some(OrderId(id)) } else { None }
    }
    fn value(self) -> u64 { self.0 }
}

impl fmt::Display for UserId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "User#{}", self.0)
    }
}

impl fmt::Display for OrderId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Order#{}", self.0)
    }
}

// Can't accidentally pass UserId where OrderId expected!
fn process_order(order: OrderId, user: UserId) -> String {
    format!("{} placed {}", user, order)
}

// === Approach 2: Newtype with validation ===
#[derive(Debug, Clone, PartialEq)]
struct Email(String);

impl Email {
    fn new(s: &str) -> Option<Self> {
        if s.contains('@') { Some(Email(s.to_string())) } else { None }
    }
    fn as_str(&self) -> &str { &self.0 }
}

// === Approach 3: Newtype for unit safety ===
#[derive(Debug, Clone, Copy, PartialEq)]
struct Celsius(f64);

#[derive(Debug, Clone, Copy, PartialEq)]
struct Fahrenheit(f64);

impl Celsius {
    fn new(v: f64) -> Self { Celsius(v) }
    fn to_fahrenheit(self) -> Fahrenheit {
        Fahrenheit(self.0 * 9.0 / 5.0 + 32.0)
    }
    fn value(self) -> f64 { self.0 }
}

impl Fahrenheit {
    fn new(v: f64) -> Self { Fahrenheit(v) }
    fn to_celsius(self) -> Celsius {
        Celsius((self.0 - 32.0) * 5.0 / 9.0)
    }
    fn value(self) -> f64 { self.0 }
}

// Implement Deref for transparent access when appropriate
use std::ops::Deref;

#[derive(Debug, Clone, PartialEq)]
struct NonEmptyString(String);

impl NonEmptyString {
    fn new(s: &str) -> Option<Self> {
        if s.is_empty() { None } else { Some(NonEmptyString(s.to_string())) }
    }
}

impl Deref for NonEmptyString {
    type Target = str;
    fn deref(&self) -> &str { &self.0 }
}

fn main() {
    let user = UserId::new(42).unwrap();
    let order = OrderId::new(100).unwrap();
    println!("{}", process_order(order, user));

    let email = Email::new("user@example.com").unwrap();
    println!("Email: {}", email.as_str());

    let c = Celsius::new(100.0);
    let f = c.to_fahrenheit();
    println!("{}ยฐC = {}ยฐF", c.value(), f.value());

    let name = NonEmptyString::new("hello").unwrap();
    println!("Length via Deref: {}", name.len()); // uses &str methods
}

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

    #[test]
    fn test_user_order_type_safety() {
        let u = UserId::new(1).unwrap();
        let o = OrderId::new(2).unwrap();
        assert_eq!(u.value(), 1);
        assert_eq!(o.value(), 2);
        // UserId and OrderId are different types
    }

    #[test]
    fn test_invalid_ids() {
        assert!(UserId::new(0).is_none());
        assert!(OrderId::new(0).is_none());
    }

    #[test]
    fn test_email_validation() {
        assert!(Email::new("a@b.com").is_some());
        assert!(Email::new("invalid").is_none());
    }

    #[test]
    fn test_temperature_conversion() {
        let c = Celsius::new(0.0);
        assert!((c.to_fahrenheit().value() - 32.0).abs() < 1e-10);

        let f = Fahrenheit::new(212.0);
        assert!((f.to_celsius().value() - 100.0).abs() < 1e-10);
    }

    #[test]
    fn test_roundtrip() {
        let c = Celsius::new(37.0);
        let back = c.to_fahrenheit().to_celsius();
        assert!((back.value() - 37.0).abs() < 1e-10);
    }

    #[test]
    fn test_non_empty_string() {
        assert!(NonEmptyString::new("hello").is_some());
        assert!(NonEmptyString::new("").is_none());
        let s = NonEmptyString::new("test").unwrap();
        assert_eq!(s.len(), 4); // Deref to &str
    }
}
(* Example 081: Newtype Pattern *)
(* OCaml module types โ†’ Rust tuple structs for type safety *)

(* Approach 1: Abstract types via modules *)
module UserId : sig
  type t
  val create : int -> t
  val value : t -> int
  val to_string : t -> string
end = struct
  type t = int
  let create x = if x > 0 then x else failwith "invalid user id"
  let value x = x
  let to_string = string_of_int
end

module OrderId : sig
  type t
  val create : int -> t
  val value : t -> int
  val to_string : t -> string
end = struct
  type t = int
  let create x = if x > 0 then x else failwith "invalid order id"
  let value x = x
  let to_string = string_of_int
end

(* Approach 2: Private type abbreviation *)
module Email : sig
  type t = private string
  val create : string -> t option
  val to_string : t -> string
end = struct
  type t = string
  let create s = if String.contains s '@' then Some s else None
  let to_string s = s
end

(* Approach 3: Record wrapper *)
type celsius = { celsius_value : float }
type fahrenheit = { fahrenheit_value : float }

let celsius_of_float v = { celsius_value = v }
let fahrenheit_of_float v = { fahrenheit_value = v }

let to_fahrenheit c =
  { fahrenheit_value = c.celsius_value *. 9.0 /. 5.0 +. 32.0 }

let to_celsius f =
  { celsius_value = (f.fahrenheit_value -. 32.0) *. 5.0 /. 9.0 }

(* Cannot accidentally mix: to_fahrenheit expects celsius *)

(* Tests *)
let () =
  let uid = UserId.create 42 in
  let oid = OrderId.create 100 in
  assert (UserId.value uid = 42);
  assert (OrderId.value oid = 100);
  (* UserId and OrderId are incompatible types *)

  (match Email.create "user@example.com" with
   | Some e -> assert (Email.to_string e = "user@example.com")
   | None -> assert false);
  assert (Email.create "invalid" = None);

  let c = celsius_of_float 100.0 in
  let f = to_fahrenheit c in
  assert (abs_float (f.fahrenheit_value -. 212.0) < 0.01);
  let c2 = to_celsius f in
  assert (abs_float (c2.celsius_value -. 100.0) < 0.01);

  Printf.printf "โœ“ All tests passed\n"

๐Ÿ“Š Detailed Comparison

Comparison: Newtype Pattern

Abstract Type vs Tuple Struct

OCaml:

๐Ÿช Show OCaml equivalent
module UserId : sig
type t
val create : int -> t
val value : t -> int
end = struct
type t = int
let create x = if x > 0 then x else failwith "invalid"
let value x = x
end

Rust:

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct UserId(u64);

impl UserId {
 fn new(id: u64) -> Option<Self> {
     if id > 0 { Some(UserId(id)) } else { None }
 }
 fn value(self) -> u64 { self.0 }
}

Temperature Units

OCaml:

๐Ÿช Show OCaml equivalent
type celsius = { celsius_value : float }
type fahrenheit = { fahrenheit_value : float }

let to_fahrenheit c =
{ fahrenheit_value = c.celsius_value *. 9.0 /. 5.0 +. 32.0 }

Rust:

struct Celsius(f64);
struct Fahrenheit(f64);

impl Celsius {
 fn to_fahrenheit(self) -> Fahrenheit {
     Fahrenheit(self.0 * 9.0 / 5.0 + 32.0)
 }
}

Validated Data

OCaml:

๐Ÿช Show OCaml equivalent
module Email : sig
type t = private string
val create : string -> t option
end = struct
type t = string
let create s = if String.contains s '@' then Some s else None
end

Rust:

struct Email(String);

impl Email {
 fn new(s: &str) -> Option<Self> {
     if s.contains('@') { Some(Email(s.to_string())) } else { None }
 }
}