🦀 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

066 — Phantom Types

Difficulty: ⭐⭐⭐ Advanced Category: Functors and modules Concept: Using phantom type parameters for compile-time unit safety Key Insight: Both languages support phantom types, but Rust requires `PhantomData<T>` to satisfy the compiler's "unused type parameter" rule — making the phantom explicit rather than implicit.

Run

cargo test
/// # Phantom Types — Type-Safe Units
///
/// Phantom type parameters exist only at the type level — they carry no runtime data
/// but prevent mixing incompatible values (e.g., meters + seconds) at compile time.

use std::marker::PhantomData;
use std::ops::Add;

/// Unit marker types — zero-sized, exist only for the type system.
#[derive(Clone, Copy)]
pub struct Meters;
#[derive(Clone, Copy)]
pub struct Seconds;

/// A quantity tagged with a phantom unit type.
/// `PhantomData<U>` tells the compiler we "use" U without storing it.
#[derive(Debug, Clone, Copy)]
pub struct Quantity<U> {
    value: f64,
    _unit: PhantomData<U>,
}

impl<U> Quantity<U> {
    pub fn new(value: f64) -> Self {
        Quantity {
            value,
            _unit: PhantomData,
        }
    }

    pub fn value(&self) -> f64 {
        self.value
    }

    /// Scale by a dimensionless factor — preserves the unit type.
    pub fn scale(&self, factor: f64) -> Self {
        Quantity::new(self.value * factor)
    }
}

/// Addition is only defined for quantities of the SAME unit.
/// Trying to add Quantity<Meters> + Quantity<Seconds> is a compile error!
impl<U> Add for Quantity<U> {
    type Output = Self;
    fn add(self, rhs: Self) -> Self {
        Quantity::new(self.value + rhs.value)
    }
}

/// Convenience constructors
pub fn meters(v: f64) -> Quantity<Meters> {
    Quantity::new(v)
}

pub fn seconds(v: f64) -> Quantity<Seconds> {
    Quantity::new(v)
}

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

    #[test]
    fn test_add_same_units() {
        let d1 = meters(100.0);
        let d2 = meters(50.0);
        let total = d1 + d2;
        assert!((total.value() - 150.0).abs() < f64::EPSILON);
    }

    #[test]
    fn test_scale() {
        let t = seconds(3.0);
        let doubled = t.scale(2.0);
        assert!((doubled.value() - 6.0).abs() < f64::EPSILON);
    }

    #[test]
    fn test_cannot_add_different_units() {
        // This would fail to compile:
        // let _ = meters(1.0) + seconds(2.0);
        // Error: expected `Quantity<Meters>`, found `Quantity<Seconds>`
        assert!(true); // Compile-time safety — the test is that it compiles
    }

    #[test]
    fn test_zero_sized() {
        // PhantomData<U> is zero-sized — Quantity is just an f64
        assert_eq!(
            std::mem::size_of::<Quantity<Meters>>(),
            std::mem::size_of::<f64>()
        );
    }

    #[test]
    fn test_copy_semantics() {
        let d = meters(42.0);
        let d2 = d; // Copy, not move
        assert!((d.value() - d2.value()).abs() < f64::EPSILON);
    }
}
(* Phantom Types — Type-Safe Units *)
(* The phantom type parameter 'a exists only at the type level *)

type meters
type seconds
type 'a quantity = Q of float

let meters x : meters quantity = Q x
let seconds x : seconds quantity = Q x

let add (Q a : 'a quantity) (Q b : 'a quantity) : 'a quantity = Q (a +. b)
let scale k (Q a : 'a quantity) : 'a quantity = Q (k *. a)
let value (Q x) = x

let () =
  let d1 = meters 100.0 in
  let d2 = meters 50.0 in
  let total = add d1 d2 in
  Printf.printf "Distance: %.1f m\n" (value total);
  (* add d1 (seconds 5.0) would be a type error! *)
  let doubled = scale 2.0 (seconds 3.0) in
  Printf.printf "Time: %.1f s\n" (value doubled);
  Printf.printf "Phantom type tests passed!\n"

📊 Detailed Comparison

Phantom Types — OCaml vs Rust Comparison

Core Insight

Phantom types let you encode invariants in the type system with zero runtime cost. Both OCaml and Rust support them, but Rust requires explicit `PhantomData<T>` marker while OCaml allows unused type parameters directly. The result is the same: the compiler prevents you from adding meters to seconds.

OCaml Approach

Declares abstract types (`type meters`, `type seconds`) with no constructors — they exist purely at the type level. The `'a quantity` type carries the phantom parameter in its type signature. OCaml allows unused type parameters without complaint, making the pattern lightweight.

Rust Approach

Uses zero-sized marker structs (`struct Meters;`) and `PhantomData<U>` in the quantity struct. `PhantomData` is a zero-sized type that tells the compiler "I logically use U" without actually storing data. Implementing `Add` trait only for same-unit quantities enforces safety through the trait system.

Comparison Table

AspectOCamlRust
MemorySame as `float`Same as `f64` (PhantomData is ZST)
Null safetyNot applicableNot applicable
ErrorsType error at compile timeType error at compile time
IterationN/AN/A
MarkerAbstract `type meters``struct Meters;` + `PhantomData`

Things Rust Learners Should Notice

1. `PhantomData<T>` is zero-sized — it compiles away completely, `Quantity` is just an `f64` 2. Marker structs — `struct Meters;` (unit struct) carries no data, exists only for types 3. Trait bounds on `Add` — implementing `Add for Quantity<U>` ensures same-unit addition 4. `Copy` + `Clone` can be derived since all fields are Copy (including PhantomData) 5. Compile-time guarantee — `meters(1.0) + seconds(2.0)` literally cannot compile

Further Reading

  • [PhantomData](https://doc.rust-lang.org/std/marker/struct.PhantomData.html)
  • [Rust by Example: Phantom types](https://doc.rust-lang.org/rust-by-example/generics/phantom.html)
  • [Zero-Sized Types](https://doc.rust-lang.org/nomicon/exotic-sizes.html#zero-sized-types-zsts)