// Example 132: Units of Measure via Phantom Types
use std::marker::PhantomData;
use std::ops::{Add, Mul, Div};
// Approach 1: Phantom type units
struct Meters;
struct Seconds;
struct Kilograms;
struct MetersPerSecond;
#[derive(Debug, Clone, Copy)]
struct Quantity<Unit> {
value: f64,
_unit: PhantomData<Unit>,
}
impl<U> Quantity<U> {
fn new(value: f64) -> Self {
Quantity { value, _unit: PhantomData }
}
fn value(&self) -> f64 {
self.value
}
}
// Same-unit addition
impl<U> Add for Quantity<U> {
type Output = Quantity<U>;
fn add(self, rhs: Self) -> Self::Output {
Quantity::new(self.value + rhs.value)
}
}
// Scalar multiplication
impl<U> Mul<f64> for Quantity<U> {
type Output = Quantity<U>;
fn mul(self, rhs: f64) -> Self::Output {
Quantity::new(self.value * rhs)
}
}
// Distance / Time = Speed
impl Div<Quantity<Seconds>> for Quantity<Meters> {
type Output = Quantity<MetersPerSecond>;
fn div(self, rhs: Quantity<Seconds>) -> Self::Output {
Quantity::new(self.value / rhs.value)
}
}
// Speed * Time = Distance
impl Mul<Quantity<Seconds>> for Quantity<MetersPerSecond> {
type Output = Quantity<Meters>;
fn mul(self, rhs: Quantity<Seconds>) -> Self::Output {
Quantity::new(self.value * rhs.value)
}
}
// Approach 2: Generic unit display
trait UnitName {
fn name() -> &'static str;
}
impl UnitName for Meters { fn name() -> &'static str { "m" } }
impl UnitName for Seconds { fn name() -> &'static str { "s" } }
impl UnitName for Kilograms { fn name() -> &'static str { "kg" } }
impl UnitName for MetersPerSecond { fn name() -> &'static str { "m/s" } }
impl<U: UnitName> std::fmt::Display for Quantity<U> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:.2} {}", self.value, U::name())
}
}
// Approach 3: Conversion between units
struct Kilometers;
struct Miles;
impl UnitName for Kilometers { fn name() -> &'static str { "km" } }
impl UnitName for Miles { fn name() -> &'static str { "mi" } }
trait ConvertTo<Target> {
fn convert(self) -> Quantity<Target>;
}
impl ConvertTo<Kilometers> for Quantity<Meters> {
fn convert(self) -> Quantity<Kilometers> {
Quantity::new(self.value / 1000.0)
}
}
impl ConvertTo<Miles> for Quantity<Kilometers> {
fn convert(self) -> Quantity<Miles> {
Quantity::new(self.value * 0.621371)
}
}
fn main() {
let distance: Quantity<Meters> = Quantity::new(100.0);
let distance2: Quantity<Meters> = Quantity::new(50.0);
let total = distance + distance2;
println!("Total: {}", total);
let time: Quantity<Seconds> = Quantity::new(10.0);
let speed = total / time;
println!("Speed: {}", speed);
let new_dist = speed * Quantity::<Seconds>::new(5.0);
println!("New distance: {}", new_dist);
let km: Quantity<Kilometers> = Quantity::<Meters>::new(5000.0).convert();
let mi: Quantity<Miles> = km.convert();
println!("{} = {}", km, mi);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_same_unit_add() {
let a: Quantity<Meters> = Quantity::new(10.0);
let b: Quantity<Meters> = Quantity::new(20.0);
assert_eq!((a + b).value(), 30.0);
}
#[test]
fn test_scalar_mul() {
let a: Quantity<Meters> = Quantity::new(5.0);
assert_eq!((a * 3.0).value(), 15.0);
}
#[test]
fn test_speed_computation() {
let d = Quantity::<Meters>::new(100.0);
let t = Quantity::<Seconds>::new(10.0);
let s = d / t;
assert_eq!(s.value(), 10.0);
}
#[test]
fn test_speed_times_time() {
let s = Quantity::<MetersPerSecond>::new(5.0);
let t = Quantity::<Seconds>::new(10.0);
let d = s * t;
assert_eq!(d.value(), 50.0);
}
#[test]
fn test_conversion() {
let m = Quantity::<Meters>::new(1000.0);
let km: Quantity<Kilometers> = m.convert();
assert_eq!(km.value(), 1.0);
}
}
(* Example 132: Units of Measure via Phantom Types *)
(* Approach 1: Phantom type units *)
type meters
type seconds
type kilograms
type 'unit quantity = { value : float }
let meters v : meters quantity = { value = v }
let seconds v : seconds quantity = { value = v }
let kilograms v : kilograms quantity = { value = v }
let add (a : 'u quantity) (b : 'u quantity) : 'u quantity =
{ value = a.value +. b.value }
let scale (a : 'u quantity) (s : float) : 'u quantity =
{ value = a.value *. s }
let get_value (q : 'u quantity) = q.value
(* Approach 2: Module-based units *)
module type UNIT = sig
type t
val name : string
end
module Meters : UNIT = struct type t = meters let name = "m" end
module Seconds : UNIT = struct type t = seconds let name = "s" end
module Quantity (U : UNIT) = struct
type t = float
let create v = v
let add a b = a +. b
let to_string v = Printf.sprintf "%.2f %s" v U.name
end
module M = Quantity(Meters)
module S = Quantity(Seconds)
(* Approach 3: Speed = meters / seconds *)
type speed
let compute_speed (d : meters quantity) (t : seconds quantity) : speed quantity =
{ value = d.value /. t.value }
(* Tests *)
let () =
let d1 = meters 100.0 in
let d2 = meters 50.0 in
let total = add d1 d2 in
assert (get_value total = 150.0);
let t = seconds 10.0 in
let s = compute_speed total t in
assert (get_value s = 15.0);
let scaled = scale d1 2.0 in
assert (get_value scaled = 200.0);
(* Module-based *)
let m1 = M.create 5.0 in
let m2 = M.create 3.0 in
assert (M.add m1 m2 = 8.0);
Printf.printf "โ All tests passed\n"