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