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

135: Generic Newtype Patterns

Difficulty: โญโญ Level: Intermediate Wrap primitives and collections in named types to prevent mix-ups, add invariants, and give behavior to types you don't own.

The Problem This Solves

You have a user ID and a product ID, both stored as `u64`. A function accepts a user ID. Nothing in the type system stops you from passing a product ID โ€” they're both `u64`. You find the bug when a product ID gets stored in a user table at runtime. Or you have a `String` email field and a `String` username field in a struct. Can you accidentally assign one to the other? Yes. The compiler sees the same type. Any `String` fits where any other `String` is expected. Newtypes fix both problems. `struct UserId(u64)` and `struct ProductId(u64)` are different types. You can't pass one where the other is expected. The cost is essentially zero โ€” Rust typically compiles newtypes to the same machine code as the inner type. As a bonus, you can attach validation logic: `Email::new(s)` returns a `Result` and an `Email` can only be constructed from a valid email string. Once you have an `Email`, you know it's valid โ€” no need to re-validate.

The Intuition

A newtype is a single-field tuple struct wrapping another type: `struct Email(String)`. That's it. The wrapper has zero runtime overhead โ€” it compiles to the same memory layout. But the compiler treats it as a completely distinct type. You choose what to expose. Implement `Deref<Target = String>` to let it behave like a `String` in read-only contexts. Implement `AsRef<str>` for interop. Keep the constructor private and expose only a validated `Email::new()`. Add custom `Display`, `Debug`, `PartialEq` impls as needed. Generic newtypes extend this further: `struct SortedVec<T: Ord>(Vec<T>)` wraps a `Vec` and guarantees it's always sorted. The invariant is maintained because the only way to construct or modify the inner vec is through the newtype's methods.

How It Works in Rust

// Basic newtype โ€” different type, zero runtime overhead
#[derive(Debug, Clone, PartialEq)]
struct Email(String);

impl Email {
 // Constructor validates the invariant โ€” only way to create an Email
 fn new(s: &str) -> Result<Self, &'static str> {
     if s.contains('@') { Ok(Email(s.to_string())) }
     else { Err("Invalid email: missing @") }
 }
 fn as_str(&self) -> &str { &self.0 }
}

// You CAN'T write: let e: Email = "hello".to_string(); โ€” different type
// You CAN write: let e = Email::new("user@example.com")?;

// Deref lets a newtype behave like its inner type in read-only contexts
use std::ops::Deref;

struct Username(String);
impl Deref for Username {
 type Target = str;
 fn deref(&self) -> &str { &self.0 }  // Username transparently becomes &str
}

let name = Username::new("alice").unwrap();
let len = name.len();  // calls str::len() via Deref โ€” no boilerplate needed

// Generic newtype that maintains a sorted invariant
#[derive(Debug, Clone)]
struct SortedVec<T: Ord> {
 inner: Vec<T>,
}

impl<T: Ord> SortedVec<T> {
 fn new() -> Self { SortedVec { inner: vec![] } }

 fn from_vec(mut v: Vec<T>) -> Self {
     v.sort();                    // sort on construction โ€” invariant established
     SortedVec { inner: v }
 }

 fn insert(&mut self, val: T) {
     // Binary search maintains sort order on insert โ€” invariant preserved
     let pos = self.inner.binary_search(&val).unwrap_or_else(|e| e);
     self.inner.insert(pos, val);
 }

 // min/max are free when sorted โ€” no scanning needed
 fn min(&self) -> Option<&T> { self.inner.first() }
 fn max(&self) -> Option<&T> { self.inner.last() }

 // Expose inner slice read-only โ€” callers can't break sort order
 fn as_slice(&self) -> &[T] { &self.inner }
}
Usage:
let mut sv = SortedVec::from_vec(vec![3, 1, 4, 1, 5]);
// sv.inner is always sorted: [1, 1, 3, 4, 5]
sv.insert(2);
// [1, 1, 2, 3, 4, 5]

// Non-empty wrapper โ€” provably non-empty at compile time
struct NonEmpty<T> { head: T, tail: Vec<T> }
impl<T> NonEmpty<T> {
 fn first(&self) -> &T { &self.head }   // always safe โ€” no Option needed
}

What This Unlocks

Key Differences

ConceptOCamlRust
Basic newtype`type email = Email of string` โ€” algebraic type`struct Email(String)` โ€” tuple struct
ConstructorPattern match to deconstruct: `let Email s = e``e.0` or expose a method; field is private by default
Deref / inheritanceModules, or explicit field access`impl Deref` lets newtype act as inner type in read contexts
Generic wrapper`module SortedList` with explicit `of_list``struct SortedVec<T: Ord>` with trait bounds on T
// Example 135: Generic Newtype Patterns

use std::fmt;
use std::ops::Deref;

// Approach 1: Validated newtypes
#[derive(Debug, Clone, PartialEq)]
struct Email(String);

impl Email {
    fn new(s: &str) -> Result<Self, &'static str> {
        if s.contains('@') { Ok(Email(s.to_string())) }
        else { Err("Invalid email") }
    }
    fn as_str(&self) -> &str { &self.0 }
}

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

impl Username {
    fn new(s: &str) -> Result<Self, &'static str> {
        if s.len() >= 3 { Ok(Username(s.to_string())) }
        else { Err("Username too short") }
    }
}

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

// Approach 2: Generic validated wrapper
#[derive(Debug, Clone, PartialEq)]
struct Validated<T> {
    inner: T,
}

trait Validate: Sized {
    type Error;
    fn validate(self) -> Result<Validated<Self>, Self::Error>;
}

#[derive(Debug, Clone, PartialEq)]
struct PositiveInt(i32);

impl Validate for i32 {
    type Error = &'static str;
    fn validate(self) -> Result<Validated<i32>, &'static str> {
        if self > 0 { Ok(Validated { inner: self }) }
        else { Err("Must be positive") }
    }
}

// Approach 3: Newtype that adds behavior to external types
#[derive(Debug, Clone)]
struct SortedVec<T: Ord> {
    inner: Vec<T>,
}

impl<T: Ord> SortedVec<T> {
    fn new() -> Self { SortedVec { inner: vec![] } }

    fn from_vec(mut v: Vec<T>) -> Self {
        v.sort();
        SortedVec { inner: v }
    }

    fn insert(&mut self, val: T) {
        let pos = self.inner.binary_search(&val).unwrap_or_else(|e| e);
        self.inner.insert(pos, val);
    }

    fn contains(&self, val: &T) -> bool {
        self.inner.binary_search(val).is_ok()
    }

    fn min(&self) -> Option<&T> { self.inner.first() }
    fn max(&self) -> Option<&T> { self.inner.last() }
    fn as_slice(&self) -> &[T] { &self.inner }
}

impl<T: Ord + fmt::Display> fmt::Display for SortedVec<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{:?}", self.inner.iter().map(|x| format!("{}", x)).collect::<Vec<_>>())
    }
}

// NonEmpty wrapper
#[derive(Debug, Clone)]
struct NonEmpty<T> {
    head: T,
    tail: Vec<T>,
}

impl<T> NonEmpty<T> {
    fn new(head: T) -> Self { NonEmpty { head, tail: vec![] } }

    fn from_vec(v: Vec<T>) -> Option<Self> {
        let mut iter = v.into_iter();
        iter.next().map(|head| NonEmpty { head, tail: iter.collect() })
    }

    fn first(&self) -> &T { &self.head }
    fn len(&self) -> usize { 1 + self.tail.len() }
}

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

    let name = Username::new("alice").unwrap();
    println!("Username len: {}", name.len()); // Deref to str

    let mut sv = SortedVec::from_vec(vec![3, 1, 4, 1, 5]);
    println!("Sorted: {:?}", sv.as_slice());
    sv.insert(2);
    println!("After insert 2: {:?}", sv.as_slice());
    println!("Min: {:?}, Max: {:?}", sv.min(), sv.max());

    let ne = NonEmpty::from_vec(vec![1, 2, 3]).unwrap();
    println!("NonEmpty first: {}, len: {}", ne.first(), ne.len());
}

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

    #[test]
    fn test_email_validation() {
        assert!(Email::new("user@test.com").is_ok());
        assert!(Email::new("invalid").is_err());
    }

    #[test]
    fn test_username_validation() {
        assert!(Username::new("abc").is_ok());
        assert!(Username::new("ab").is_err());
    }

    #[test]
    fn test_username_deref() {
        let u = Username::new("alice").unwrap();
        assert_eq!(u.len(), 5); // Deref to str
    }

    #[test]
    fn test_sorted_vec() {
        let sv = SortedVec::from_vec(vec![3, 1, 4, 1, 5]);
        assert_eq!(sv.as_slice(), &[1, 1, 3, 4, 5]);
        assert_eq!(sv.min(), Some(&1));
        assert_eq!(sv.max(), Some(&5));
    }

    #[test]
    fn test_sorted_vec_insert() {
        let mut sv = SortedVec::from_vec(vec![1, 3, 5]);
        sv.insert(2);
        assert_eq!(sv.as_slice(), &[1, 2, 3, 5]);
    }

    #[test]
    fn test_non_empty() {
        let ne = NonEmpty::from_vec(vec![10, 20, 30]).unwrap();
        assert_eq!(*ne.first(), 10);
        assert_eq!(ne.len(), 3);
        assert!(NonEmpty::<i32>::from_vec(vec![]).is_none());
    }

    #[test]
    fn test_validate() {
        assert!(5i32.validate().is_ok());
        assert!((-1i32).validate().is_err());
    }
}
(* Example 135: Generic Newtype Patterns *)

(* Approach 1: Private type aliases *)
type email = Email of string
type username = Username of string
type user_id = UserId of int

let email_of_string s =
  if String.contains s '@' then Some (Email s) else None

let string_of_email (Email s) = s

let username_of_string s =
  if String.length s >= 3 then Some (Username s) else None

let string_of_username (Username s) = s

(* Approach 2: Functor-based generic wrapper *)
module type WRAPPER = sig
  type inner
  type t
  val wrap : inner -> t
  val unwrap : t -> inner
end

module MakeWrapper (I : sig type t end) : WRAPPER with type inner = I.t = struct
  type inner = I.t
  type t = inner
  let wrap x = x
  let unwrap x = x
end

module PositiveInt = struct
  type t = int
  let create n = if n > 0 then Some n else None
  let value x = x
end

(* Approach 3: Sorted list newtype *)
module SortedList = struct
  type 'a t = 'a list  (* invariant: always sorted *)
  let empty = []
  let of_list lst = List.sort compare lst
  let insert x lst = List.sort compare (x :: lst)
  let to_list t = t
  let min = function [] -> None | x :: _ -> Some x
end

(* Tests *)
let () =
  assert (email_of_string "test@example.com" <> None);
  assert (email_of_string "invalid" = None);
  assert (string_of_email (Email "a@b.com") = "a@b.com");
  assert (username_of_string "ab" = None);
  assert (username_of_string "abc" <> None);
  assert (PositiveInt.create 5 = Some 5);
  assert (PositiveInt.create (-1) = None);
  let sl = SortedList.of_list [3;1;4;1;5] in
  assert (SortedList.to_list sl = [1;1;3;4;5]);
  assert (SortedList.min sl = Some 1);
  Printf.printf "โœ“ All tests passed\n"

๐Ÿ“Š Detailed Comparison

Comparison: Generic Newtype Patterns

OCaml

๐Ÿช Show OCaml equivalent
type email = Email of string

let email_of_string s =
if String.contains s '@' then Some (Email s) else None

let string_of_email (Email s) = s

module SortedList = struct
type 'a t = 'a list
let of_list lst = List.sort compare lst
let insert x lst = List.sort compare (x :: lst)
end

Rust

struct Email(String);

impl Email {
 fn new(s: &str) -> Result<Self, &'static str> {
     if s.contains('@') { Ok(Email(s.to_string())) }
     else { Err("Invalid email") }
 }
}

struct SortedVec<T: Ord> { inner: Vec<T> }

impl<T: Ord> SortedVec<T> {
 fn from_vec(mut v: Vec<T>) -> Self { v.sort(); SortedVec { inner: v } }
 fn insert(&mut self, val: T) {
     let pos = self.inner.binary_search(&val).unwrap_or_else(|e| e);
     self.inner.insert(pos, val);
 }
}