// 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"