Home/Advanced Trait System/Orphan Rules & Newtype

Orphan Rules & Newtype

Working with external traits

intermediate
orphan-rulesnewtypewrapper
🎮 Interactive Playground

What is the Orphan Rule and Newtype Pattern?

The orphan rule is Rust's trait coherence rule that prevents you from implementing a trait for a type unless either the trait or the type is defined in your crate. The newtype pattern is the elegant workaround: wrap a foreign type in a local tuple struct to regain implementation rights while maintaining zero-cost abstraction.

use std::fmt::Display;

// ❌ ORPHAN RULE VIOLATION - Won't compile!
// Neither Vec nor Display are local to this crate
impl Display for Vec<i32> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:?}", self)
    }
}
// Error: only traits defined in the current crate can be implemented for types
// defined outside of the crate

// ✅ NEWTYPE PATTERN - This compiles!
// IntList is local, so we can implement Display for it
struct IntList(Vec<i32>);

impl Display for IntList {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "[{}]", self.0.iter()
            .map(|n| n.to_string())
            .collect::<Vec<_>>()
            .join(", "))
    }
}

// Now we can use it:
let numbers = IntList(vec![1, 2, 3, 4, 5]);
println!("{}", numbers);  // Prints: [1, 2, 3, 4, 5]
The orphan rule exists to prevent conflicts: Without it, two different crates could implement the same trait for the same type differently, creating ambiguity about which implementation to use. Key characteristics:
  • Coherence guarantee: No two implementations of a trait can exist for the same type
  • Local control: At least one of (trait, type) must be local to your crate
  • Zero-cost wrapper: Newtypes have no runtime overhead with #[repr(transparent)]
  • Type safety bonus: Newtypes provide semantic type distinctions (UserId vs OrderId)
When you hit the orphan rule:
// You control neither trait nor type:
impl serde::Serialize for uuid::Uuid { }  // ❌ Both foreign
impl Display for std::path::PathBuf { }   // ❌ Both foreign
impl From<String> for std::net::IpAddr { }  // ❌ Both foreign

// Solutions with newtypes:
struct MyUuid(uuid::Uuid);
impl serde::Serialize for MyUuid { }  // ✅ MyUuid is local

struct MyPath(std::path::PathBuf);
impl Display for MyPath { }  // ✅ MyPath is local

struct IpAddress(std::net::IpAddr);
impl From<String> for IpAddress { }  // ✅ IpAddress is local

Real-World Examples

1. JSON Serialization for External Types (Web/Backend)

When building REST APIs, you often need custom JSON serialization for types from external crates:

use serde::{Serialize, Deserialize, Serializer};
use uuid::Uuid;
use std::fmt;

// ❌ Can't do this - orphan rule violation:
// impl Serialize for Uuid { /* custom format */ }

// ✅ Newtype wrapper gives us implementation rights
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(transparent)]  // Same memory layout as Uuid
struct UserId(Uuid);

impl UserId {
    pub fn new() -> Self {
        Self(Uuid::new_v4())
    }
    
    pub fn from_uuid(uuid: Uuid) -> Self {
        Self(uuid)
    }
}

// Now we can implement Serialize with custom formatting
impl Serialize for UserId {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        // Custom format: uppercase with no hyphens
        let s = self.0.to_string().replace("-", "").to_uppercase();
        serializer.serialize_str(&s)
    }
}

// Deserialize from various formats
impl<'de> Deserialize<'de> for UserId {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        // Accept with or without hyphens
        let uuid = Uuid::parse_str(&s)
            .or_else(|_| {
                // Try inserting hyphens at standard positions
                if s.len() == 32 {
                    let formatted = format!(
                        "{}-{}-{}-{}-{}",
                        &s[0..8], &s[8..12], &s[12..16], &s[16..20], &s[20..32]
                    );
                    Uuid::parse_str(&formatted)
                } else {
                    Err(uuid::Error::InvalidLength(s.len()))
                }
            })
            .map_err(serde::de::Error::custom)?;
        Ok(UserId(uuid))
    }
}

// Deref for ergonomic access to inner Uuid methods
impl std::ops::Deref for UserId {
    type Target = Uuid;
    
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

// Display for debugging and logging
impl fmt::Display for UserId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "User({})", self.0)
    }
}

// Usage in API:
use serde_json;

#[derive(Serialize, Deserialize)]
struct User {
    id: UserId,
    username: String,
    email: String,
}

fn example_api_usage() {
    let user = User {
        id: UserId::new(),
        username: "rustacean".to_string(),
        email: "user@example.com".to_string(),
    };
    
    // Serializes with custom UUID format (no hyphens, uppercase)
    let json = serde_json::to_string(&user).unwrap();
    println!("JSON: {}", json);
    // {"id":"A1B2C3D4E5F67890A1B2C3D4E5F67890","username":"rustacean",...}
    
    // Can deserialize from various formats
    let json_with_hyphens = r#"{"id":"a1b2c3d4-e5f6-7890-a1b2-c3d4e5f67890","username":"rustacean","email":"user@example.com"}"#;
    let parsed: User = serde_json::from_str(json_with_hyphens).unwrap();
    
    // Deref coercion lets us call Uuid methods directly
    println!("Version: {}", parsed.id.get_version_num());  // Deref to Uuid
}
Why this matters: Different systems may expect different UUID formats. The newtype lets you enforce your API's specific serialization format while still benefiting from the uuid crate's functionality.

2. Foreign Database Types (Database/ORM)

ORMs like Diesel require implementing traits for your database column types:

use chrono::{DateTime, Utc, NaiveDateTime};
use std::fmt;

// Simulating diesel traits (actual diesel has more complex traits)
trait FromSql<ST, DB> {
    fn from_sql(bytes: &[u8]) -> Result<Self, String>
    where
        Self: Sized;
}

trait ToSql<ST, DB> {
    fn to_sql(&self) -> Result<Vec<u8>, String>;
}

// Mock diesel types
struct Timestamp;
struct Pg; // PostgreSQL backend

// ❌ Can't implement diesel traits for chrono types - orphan rule!
// impl FromSql<Timestamp, Pg> for DateTime<Utc> { }

// ✅ Newtype wrapper solves the orphan rule
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
struct DbTimestamp(DateTime<Utc>);

impl DbTimestamp {
    pub fn now() -> Self {
        Self(Utc::now())
    }
    
    pub fn from_datetime(dt: DateTime<Utc>) -> Self {
        Self(dt)
    }
    
    pub fn into_inner(self) -> DateTime<Utc> {
        self.0
    }
}

impl FromSql<Timestamp, Pg> for DbTimestamp {
    fn from_sql(bytes: &[u8]) -> Result<Self, String> {
        // Parse PostgreSQL timestamp format
        let s = std::str::from_utf8(bytes)
            .map_err(|e| format!("Invalid UTF-8: {}", e))?;
        
        let naive = NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S")
            .map_err(|e| format!("Parse error: {}", e))?;
        
        let dt = DateTime::<Utc>::from_naive_utc_and_offset(naive, Utc);
        Ok(DbTimestamp(dt))
    }
}

impl ToSql<Timestamp, Pg> for DbTimestamp {
    fn to_sql(&self) -> Result<Vec<u8>, String> {
        // Format for PostgreSQL
        let formatted = self.0.format("%Y-%m-%d %H:%M:%S").to_string();
        Ok(formatted.into_bytes())
    }
}

// Deref for easy access to DateTime methods
impl std::ops::Deref for DbTimestamp {
    type Target = DateTime<Utc>;
    
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl fmt::Display for DbTimestamp {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0.format("%Y-%m-%d %H:%M:%S UTC"))
    }
}

// Usage in database models:
#[derive(Debug)]
struct Article {
    id: i32,
    title: String,
    created_at: DbTimestamp,
    updated_at: DbTimestamp,
}

fn example_database_usage() {
    let article = Article {
        id: 1,
        title: "Understanding the Orphan Rule".to_string(),
        created_at: DbTimestamp::now(),
        updated_at: DbTimestamp::now(),
    };
    
    // Can use DateTime methods through Deref
    println!("Created: {} (timestamp: {})", 
        article.created_at,
        article.created_at.timestamp()  // DateTime method via Deref
    );
    
    // Serialization for database storage
    let bytes = article.created_at.to_sql().unwrap();
    println!("DB bytes: {:?}", String::from_utf8(bytes.clone()));
    
    // Deserialization from database
    let from_db = DbTimestamp::from_sql(&bytes).unwrap();
    assert_eq!(article.created_at, from_db);
}
Real-world context: Diesel, SQLx, and other ORMs require custom type mappings between Rust types and database column types. The newtype pattern lets you bridge this gap without forking upstream crates.

3. Validation Wrappers (Security/Validation)

Type-safe validation using newtypes prevents invalid data from entering your system:

use std::fmt;
use std::str::FromStr;
use regex::Regex;

// Email newtype with validation
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct Email(String);

#[derive(Debug, Clone)]
pub struct EmailError(String);

impl fmt::Display for EmailError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Invalid email: {}", self.0)
    }
}

impl std::error::Error for EmailError {}

impl Email {
    // Private constructor ensures validation
    fn new(email: String) -> Result<Self, EmailError> {
        // Simple email validation (use a proper library in production)
        let re = Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}__CODE_BLOCK_0__quot;)
            .unwrap();
        
        if re.is_match(&email) {
            Ok(Email(email))
        } else {
            Err(EmailError(format!("'{}' is not a valid email", email)))
        }
    }
    
    pub fn as_str(&self) -> &str {
        &self.0
    }
    
    pub fn domain(&self) -> &str {
        self.0.split('@').nth(1).unwrap_or("")
    }
}

// Parse, don't validate pattern
impl FromStr for Email {
    type Err = EmailError;
    
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Email::new(s.to_string())
    }
}

impl fmt::Display for Email {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

// AsRef for compatibility with APIs expecting &str
impl AsRef<str> for Email {
    fn as_ref(&self) -> &str {
        &self.0
    }
}

// PhoneNumber newtype
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PhoneNumber(String);

#[derive(Debug)]
pub struct PhoneError(String);

impl fmt::Display for PhoneError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Invalid phone number: {}", self.0)
    }
}

impl std::error::Error for PhoneError {}

impl PhoneNumber {
    pub fn new(number: String) -> Result<Self, PhoneError> {
        // Strip all non-digit characters
        let digits: String = number.chars().filter(|c| c.is_numeric()).collect();
        
        // Validate length (simplified - real validation is more complex)
        if digits.len() >= 10 && digits.len() <= 15 {
            Ok(PhoneNumber(digits))
        } else {
            Err(PhoneError(format!("Phone number must be 10-15 digits, got {}", digits.len())))
        }
    }
    
    pub fn formatted(&self) -> String {
        // Format as +1-XXX-XXX-XXXX for US numbers
        if self.0.len() == 10 {
            format!("{}-{}-{}", &self.0[0..3], &self.0[3..6], &self.0[6..10])
        } else if self.0.len() == 11 && self.0.starts_with('1') {
            format!("+1-{}-{}-{}", &self.0[1..4], &self.0[4..7], &self.0[7..11])
        } else {
            self.0.clone()
        }
    }
}

impl fmt::Display for PhoneNumber {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.formatted())
    }
}

impl FromStr for PhoneNumber {
    type Err = PhoneError;
    
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        PhoneNumber::new(s.to_string())
    }
}

// URL newtype
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HttpUrl(String);

#[derive(Debug)]
pub struct UrlError(String);

impl fmt::Display for UrlError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Invalid URL: {}", self.0)
    }
}

impl std::error::Error for UrlError {}

impl HttpUrl {
    pub fn new(url: String) -> Result<Self, UrlError> {
        if url.starts_with("http://") || url.starts_with("https://") {
            // In production, use url crate for proper validation
            if url.len() > 10 && url.contains('.') {
                Ok(HttpUrl(url))
            } else {
                Err(UrlError("URL must contain a domain".to_string()))
            }
        } else {
            Err(UrlError("URL must start with http:// or https://".to_string()))
        }
    }
    
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl fmt::Display for HttpUrl {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl FromStr for HttpUrl {
    type Err = UrlError;
    
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        HttpUrl::new(s.to_string())
    }
}

// API with type-safe boundaries
#[derive(Debug)]
struct UserProfile {
    email: Email,
    phone: PhoneNumber,
    website: HttpUrl,
}

impl UserProfile {
    // Type system guarantees all fields are valid!
    pub fn new(email: Email, phone: PhoneNumber, website: HttpUrl) -> Self {
        Self { email, phone, website }
    }
}

fn example_validation_usage() {
    // ✅ Valid inputs create the types
    let email = Email::from_str("user@example.com").unwrap();
    let phone = PhoneNumber::new("555-123-4567".to_string()).unwrap();
    let website = HttpUrl::new("https://example.com".to_string()).unwrap();
    
    let profile = UserProfile::new(email, phone, website);
    println!("Profile: {:?}", profile);
    
    // ❌ Invalid inputs fail at parse time, not at business logic time
    match Email::from_str("not-an-email") {
        Ok(_) => println!("Should not happen!"),
        Err(e) => println!("Caught invalid email: {}", e),
    }
    
    match PhoneNumber::new("123".to_string()) {
        Ok(_) => println!("Should not happen!"),
        Err(e) => println!("Caught invalid phone: {}", e),
    }
    
    match HttpUrl::new("not-a-url".to_string()) {
        Ok(_) => println!("Should not happen!"),
        Err(e) => println!("Caught invalid URL: {}", e),
    }
}
The "parse, don't validate" principle: Instead of validating strings repeatedly throughout your codebase, parse them into typed newtypes once at the boundary. After that, the type system guarantees validity.

4. Network Protocol Types (Network Programming)

Custom serialization for wire protocols using newtypes:

use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::io::{self, Read, Write};
use std::fmt;

// Newtype for custom network serialization
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(transparent)]
struct ProtocolIpAddr(IpAddr);

impl ProtocolIpAddr {
    pub fn new(addr: IpAddr) -> Self {
        Self(addr)
    }
    
    // Write to wire format: 1 byte type tag + address bytes
    pub fn write_to<W: Write>(&self, writer: &mut W) -> io::Result<()> {
        match self.0 {
            IpAddr::V4(ipv4) => {
                writer.write_all(&[4])?;  // Type tag: IPv4
                writer.write_all(&ipv4.octets())?;
            }
            IpAddr::V6(ipv6) => {
                writer.write_all(&[6])?;  // Type tag: IPv6
                writer.write_all(&ipv6.octets())?;
            }
        }
        Ok(())
    }
    
    // Read from wire format
    pub fn read_from<R: Read>(reader: &mut R) -> io::Result<Self> {
        let mut type_byte = [0u8; 1];
        reader.read_exact(&mut type_byte)?;
        
        let addr = match type_byte[0] {
            4 => {
                let mut octets = [0u8; 4];
                reader.read_exact(&mut octets)?;
                IpAddr::V4(Ipv4Addr::from(octets))
            }
            6 => {
                let mut octets = [0u8; 16];
                reader.read_exact(&mut octets)?;
                IpAddr::V6(Ipv6Addr::from(octets))
            }
            other => {
                return Err(io::Error::new(
                    io::ErrorKind::InvalidData,
                    format!("Invalid IP version tag: {}", other)
                ));
            }
        };
        
        Ok(ProtocolIpAddr(addr))
    }
    
    pub fn wire_size(&self) -> usize {
        match self.0 {
            IpAddr::V4(_) => 5,   // 1 byte tag + 4 bytes
            IpAddr::V6(_) => 17,  // 1 byte tag + 16 bytes
        }
    }
}

impl std::ops::Deref for ProtocolIpAddr {
    type Target = IpAddr;
    
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl fmt::Display for ProtocolIpAddr {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

// Port number with validation
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(transparent)]
struct Port(u16);

impl Port {
    pub fn new(port: u16) -> Result<Self, String> {
        if port == 0 {
            Err("Port 0 is reserved".to_string())
        } else {
            Ok(Port(port))
        }
    }
    
    pub fn is_privileged(&self) -> bool {
        self.0 < 1024
    }
    
    pub fn is_ephemeral(&self) -> bool {
        self.0 >= 49152
    }
    
    pub fn write_to<W: Write>(&self, writer: &mut W) -> io::Result<()> {
        writer.write_all(&self.0.to_be_bytes())
    }
    
    pub fn read_from<R: Read>(reader: &mut R) -> io::Result<Self> {
        let mut bytes = [0u8; 2];
        reader.read_exact(&mut bytes)?;
        let port = u16::from_be_bytes(bytes);
        Port::new(port).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
    }
}

impl fmt::Display for Port {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

// Socket address for custom protocol
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct ProtocolSocketAddr {
    ip: ProtocolIpAddr,
    port: Port,
}

impl ProtocolSocketAddr {
    pub fn new(ip: IpAddr, port: u16) -> Result<Self, String> {
        Ok(Self {
            ip: ProtocolIpAddr::new(ip),
            port: Port::new(port)?,
        })
    }
    
    pub fn write_to<W: Write>(&self, writer: &mut W) -> io::Result<()> {
        self.ip.write_to(writer)?;
        self.port.write_to(writer)?;
        Ok(())
    }
    
    pub fn read_from<R: Read>(reader: &mut R) -> io::Result<Self> {
        let ip = ProtocolIpAddr::read_from(reader)?;
        let port = Port::read_from(reader)?;
        Ok(Self { ip, port })
    }
    
    pub fn wire_size(&self) -> usize {
        self.ip.wire_size() + 2  // IP size + 2 bytes for port
    }
}

impl fmt::Display for ProtocolSocketAddr {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}:{}", self.ip, self.port)
    }
}

fn example_network_protocol_usage() {
    use std::io::Cursor;
    
    // Create a protocol address
    let addr = ProtocolSocketAddr::new(
        IpAddr::V4(Ipv4Addr::new(192, 168, 1, 100)),
        8080
    ).unwrap();
    
    println!("Address: {} (wire size: {} bytes)", addr, addr.wire_size());
    println!("Port is privileged: {}", addr.port.is_privileged());
    
    // Serialize to wire format
    let mut buffer = Vec::new();
    addr.write_to(&mut buffer).unwrap();
    println!("Wire bytes: {:?}", buffer);
    
    // Deserialize from wire format
    let mut cursor = Cursor::new(buffer);
    let decoded = ProtocolSocketAddr::read_from(&mut cursor).unwrap();
    
    assert_eq!(addr, decoded);
    println!("Successfully round-tripped through wire format!");
    
    // IPv6 example
    let ipv6_addr = ProtocolSocketAddr::new(
        IpAddr::V6(Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 1)),
        443
    ).unwrap();
    
    println!("\nIPv6 Address: {} (wire size: {} bytes)", 
        ipv6_addr, ipv6_addr.wire_size());
}
Why custom serialization matters: Standard library types have their own serialization format (like serde), but network protocols often require specific wire formats for compatibility with other implementations or for performance.

5. Units and Measurements (Systems/Scientific)

Type-safe unit systems prevent catastrophic bugs like the Mars Climate Orbiter failure:

use std::fmt;
use std::ops::{Add, Sub, Mul, Div};

// Distance units
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
#[repr(transparent)]
struct Meters(f64);

#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
#[repr(transparent)]
struct Kilometers(f64);

impl Meters {
    pub fn new(value: f64) -> Self {
        Self(value)
    }
    
    pub fn to_kilometers(self) -> Kilometers {
        Kilometers(self.0 / 1000.0)
    }
    
    pub fn value(self) -> f64 {
        self.0
    }
}

impl Kilometers {
    pub fn new(value: f64) -> Self {
        Self(value)
    }
    
    pub fn to_meters(self) -> Meters {
        Meters(self.0 * 1000.0)
    }
    
    pub fn value(self) -> f64 {
        self.0
    }
}

// Meters can be added to meters
impl Add for Meters {
    type Output = Meters;
    
    fn add(self, other: Meters) -> Meters {
        Meters(self.0 + other.0)
    }
}

impl Sub for Meters {
    type Output = Meters;
    
    fn sub(self, other: Meters) -> Meters {
        Meters(self.0 - other.0)
    }
}

// Meters can be multiplied by scalars
impl Mul<f64> for Meters {
    type Output = Meters;
    
    fn mul(self, scalar: f64) -> Meters {
        Meters(self.0 * scalar)
    }
}

impl Div<f64> for Meters {
    type Output = Meters;
    
    fn div(self, scalar: f64) -> Meters {
        Meters(self.0 / scalar)
    }
}

// Meters divided by meters gives a ratio (dimensionless)
impl Div<Meters> for Meters {
    type Output = f64;
    
    fn div(self, other: Meters) -> f64 {
        self.0 / other.0
    }
}

impl fmt::Display for Meters {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{:.2} m", self.0)
    }
}

impl fmt::Display for Kilometers {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{:.2} km", self.0)
    }
}

// Time units
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
#[repr(transparent)]
struct Seconds(f64);

#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
#[repr(transparent)]
struct Hours(f64);

impl Seconds {
    pub fn new(value: f64) -> Self {
        Self(value)
    }
    
    pub fn to_hours(self) -> Hours {
        Hours(self.0 / 3600.0)
    }
    
    pub fn value(self) -> f64 {
        self.0
    }
}

impl Hours {
    pub fn new(value: f64) -> Self {
        Self(value)
    }
    
    pub fn to_seconds(self) -> Seconds {
        Seconds(self.0 * 3600.0)
    }
}

impl Add for Seconds {
    type Output = Seconds;
    
    fn add(self, other: Seconds) -> Seconds {
        Seconds(self.0 + other.0)
    }
}

impl fmt::Display for Seconds {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{:.2} s", self.0)
    }
}

impl fmt::Display for Hours {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{:.2} h", self.0)
    }
}

// Velocity: Distance per Time
#[derive(Debug, Clone, Copy, PartialEq)]
struct MetersPerSecond(f64);

impl MetersPerSecond {
    pub fn new(value: f64) -> Self {
        Self(value)
    }
}

// Meters divided by Seconds gives velocity
impl Div<Seconds> for Meters {
    type Output = MetersPerSecond;
    
    fn div(self, time: Seconds) -> MetersPerSecond {
        MetersPerSecond(self.0 / time.0)
    }
}

// Velocity times time gives distance
impl Mul<Seconds> for MetersPerSecond {
    type Output = Meters;
    
    fn mul(self, time: Seconds) -> Meters {
        Meters(self.0 * time.0)
    }
}

impl fmt::Display for MetersPerSecond {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{:.2} m/s", self.0)
    }
}

// Mass units
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
#[repr(transparent)]
struct Kilograms(f64);

impl Kilograms {
    pub fn new(value: f64) -> Self {
        Self(value)
    }
}

impl Add for Kilograms {
    type Output = Kilograms;
    
    fn add(self, other: Kilograms) -> Kilograms {
        Kilograms(self.0 + other.0)
    }
}

impl fmt::Display for Kilograms {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{:.2} kg", self.0)
    }
}

fn example_units_usage() {
    // Basic distance calculations
    let distance1 = Meters::new(1000.0);
    let distance2 = Meters::new(500.0);
    let total = distance1 + distance2;
    println!("Total distance: {}", total);
    
    // Unit conversions
    let km = total.to_kilometers();
    println!("In kilometers: {}", km);
    
    // ❌ Type safety: Can't add meters to seconds!
    // let wrong = distance1 + Seconds::new(10.0);  // Compile error!
    
    // ❌ Can't add meters to kilograms!
    // let wrong = distance1 + Kilograms::new(5.0);  // Compile error!
    
    // Velocity calculations
    let distance = Meters::new(100.0);
    let time = Seconds::new(10.0);
    let velocity = distance / time;
    println!("Velocity: {}", velocity);
    
    // Physics: distance = velocity * time
    let new_distance = velocity * Seconds::new(5.0);
    println!("Distance traveled in 5 seconds: {}", new_distance);
    
    // Demonstrate Mars Climate Orbiter bug prevention
    println!("\n=== Mars Climate Orbiter Bug Prevention ===");
    
    // What happened: One team used pound-force-seconds (imperial),
    // another used newton-seconds (metric), causing $327.6M loss
    
    // With newtypes, this is caught at compile time:
    let thrust_metric = Meters::new(100.0);  // Newton-seconds (conceptually)
    let thrust_imperial = Meters::new(448.0); // Pound-force-seconds (conceptually)
    
    // If we had separate types:
    // NewtonSeconds vs PoundForceSeconds
    // let total_thrust = thrust_metric + thrust_imperial;  // ❌ Compile error!
    
    println!("Type system prevents unit mismatch disasters!");
    
    // Ratios are dimensionless
    let ratio = distance1 / distance2;
    println!("\nRatio of distances: {:.2}", ratio);
}
Historical context: The Mars Climate Orbiter was lost in 1999 because one team used imperial units and another used metric units. The spacecraft burned up in Mars' atmosphere, costing $327.6 million. Type-safe unit systems prevent such disasters at compile time.

Deep Dive Explanation

What is Coherence and Why Does the Orphan Rule Exist?

Coherence is the property that for any given trait and type, there is at most one implementation. This is critical for Rust's trait system to work reliably.
// Imagine if two crates could both implement Display for Vec<i32>:

// crate_a:
impl Display for Vec<i32> {
    fn fmt(&self, f: &mut Formatter) -> Result {
        write!(f, "[{}]", self.iter().map(|x| x.to_string()).collect::<Vec<_>>().join(", "))
    }
}

// crate_b:
impl Display for Vec<i32> {
    fn fmt(&self, f: &mut Formatter) -> Result {
        write!(f, "{:?}", self)  // Different format!
    }
}

// Your crate depends on both crate_a and crate_b:
// Which implementation should the compiler use?
let v = vec![1, 2, 3];
println!("{}", v);  // ❌ Ambiguous! Compiler can't decide!
The orphan rule prevents this ambiguity by requiring that for any impl Trait for Type:
  • Either the trait is defined in your current crate (local trait)
  • OR the type is defined in your current crate (local type)
  • OR both are local

This ensures that only one crate can provide the implementation, eliminating conflicts.

What Counts as "Local"?

// Local trait examples (defined in your crate):
trait MyTrait { }
trait Display { }  // ❌ Actually foreign - from std!

// Local type examples (defined in your crate):
struct MyStruct { }
struct Wrapper(Vec<i32>);  // ✅ Wrapper is local even though Vec is foreign!
struct GenericWrapper<T>(T);  // ✅ GenericWrapper is local

// Foreign examples (from other crates):
// std::fmt::Display
// std::string::String
// std::vec::Vec
// serde::Serialize
// uuid::Uuid
Important subtlety with generics:
// ❌ This is NOT allowed even though GenericWrapper is local:
struct GenericWrapper<T>(T);
impl<T> Display for GenericWrapper<T> { }  // ❌ Still violates orphan rule!

// Why? Because another crate could do:
impl Display for Vec<i32> { }  // If this were allowed...
// And std could theoretically add:
impl<T> Display for T { }  // Blanket implementation in std

// These would conflict for GenericWrapper<Vec<i32>>!

// ✅ This IS allowed - adds a bound making it specific:
impl<T: Debug> Display for GenericWrapper<T> { }  // ✅ OK!
// The bound makes this "more specific" than a blanket impl

Scenarios Where the Orphan Rule Blocks You

// Scenario 1: Foreign trait + Foreign type
// Want to customize Display for external types
impl Display for uuid::Uuid { }  // ❌ Both foreign

// Scenario 2: Want to make external types work with external traits
impl serde::Serialize for chrono::DateTime<Utc> { }  // ❌ Both foreign

// Scenario 3: Want to implement conversion between external types
impl From<uuid::Uuid> for String { }  // ❌ Both foreign

// Scenario 4: Database ORM traits for time types
impl diesel::deserialize::FromSql<diesel::sql_types::Timestamp, diesel::pg::Pg> 
    for chrono::DateTime<Utc> { }  // ❌ All foreign

// Scenario 5: Want to extend foreign types with your trait
trait MyExt {
    fn my_method(&self);
}
impl MyExt for Vec<String> { }  // ✅ OK! MyExt is local
// This actually works! Extension traits are the exception.

The Newtype Pattern: Mechanics

The newtype pattern wraps a foreign type in a local tuple struct:

// Basic newtype: tuple struct with one field
struct Email(String);

// Can add visibility:
pub struct Email(String);  // Type is public, field is private

// Newtype with named field (less common):
struct Email { inner: String }

// Generic newtype:
struct Wrapper<T>(T);

// Newtype with lifetime:
struct StrWrapper<'a>(&'a str);

// Multiple fields (not really a newtype anymore):
struct Point(f64, f64);  // This is just a tuple struct
Memory layout guarantee:
#[repr(transparent)]  // Guarantees same layout as inner type
struct UserId(uuid::Uuid);

// Without #[repr(transparent)], the compiler *may* add padding
// With it, UserId has EXACTLY the same layout as Uuid
// Safe for FFI, transmute, etc.

use std::mem;
assert_eq!(
    mem::size_of::<UserId>(),
    mem::size_of::<uuid::Uuid>()
);
assert_eq!(
    mem::align_of::<UserId>(),
    mem::align_of::<uuid::Uuid>()
);

Making Newtypes Ergonomic: Deref and AsRef

Deref coercion lets you call inner type methods transparently:
use std::ops::Deref;

struct MyString(String);

impl Deref for MyString {
    type Target = String;
    
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

let s = MyString("hello".to_string());

// Can call String methods directly:
println!("Length: {}", s.len());  // Deref coercion to String
println!("Upper: {}", s.to_uppercase());
println!("Chars: {}", s.chars().count());

// Deref coercion in function calls:
fn takes_str(s: &str) {
    println!("Got: {}", s);
}

takes_str(&s);  // &MyString -> &String -> &str (multiple coercions!)
AsRef for explicit conversions:
impl AsRef<str> for MyString {
    fn as_ref(&self) -> &str {
        &self.0
    }
}

impl AsRef<String> for MyString {
    fn as_ref(&self) -> &String {
        &self.0
    }
}

// Usage:
fn process<S: AsRef<str>>(s: S) {
    let s: &str = s.as_ref();
    println!("Processing: {}", s);
}

process(&s);  // Works via AsRef
process("literal");  // Also works
process(String::from("owned"));  // Also works
When to use Deref vs AsRef:
  • Deref: For newtypes that are truly "just a wrapper" (UserId wrapping Uuid)
  • AsRef: For types that can be viewed as another type but aren't synonymous
Warning about Deref abuse:
// ❌ DON'T use Deref for unrelated types:
struct Stack<T>(Vec<T>);

impl<T> Deref for Stack<T> {
    type Target = Vec<T>;
    fn deref(&self) -> &Self::Target { &self.0 }
}

// This is bad! Now users can call Vec methods that break Stack invariants:
let mut stack = Stack(vec![1, 2, 3]);
stack.reverse();  // ❌ Breaks stack semantics!
stack.insert(1, 99);  // ❌ Not a stack operation!

// ✅ Instead, provide only the stack operations you want:
impl<T> Stack<T> {
    pub fn push(&mut self, value: T) { self.0.push(value); }
    pub fn pop(&mut self) -> Option<T> { self.0.pop() }
    pub fn peek(&self) -> Option<&T> { self.0.last() }
    // Don't expose Vec methods!
}

⚠️ Common Derives and Trait Implementations

// Most derives work through the inner type:
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(transparent)]
struct UserId(uuid::Uuid);

// Serde can serialize through with transparent attribute:
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
#[serde(transparent)]  // Serialize as if it were just the inner Uuid
struct UserId(uuid::Uuid);

// Manual implementations for custom behavior:
impl std::fmt::Display for UserId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "User({})", self.0)
    }
}

impl std::str::FromStr for UserId {
    type Err = uuid::Error;
    
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(UserId(uuid::Uuid::parse_str(s)?))
    }
}

// Conversion traits:
impl From<uuid::Uuid> for UserId {
    fn from(uuid: uuid::Uuid) -> Self {
        UserId(uuid)
    }
}

impl From<UserId> for uuid::Uuid {
    fn from(user_id: UserId) -> Self {
        user_id.0
    }
}

// AsRef and Borrow:
impl AsRef<uuid::Uuid> for UserId {
    fn as_ref(&self) -> &uuid::Uuid {
        &self.0
    }
}

impl std::borrow::Borrow<uuid::Uuid> for UserId {
    fn borrow(&self) -> &uuid::Uuid {
        &self.0
    }
}

When to Use

Use the newtype pattern when:

  1. Orphan rule blocks you: You need to implement a foreign trait for a foreign type
  2. Semantic type safety: UserId vs OrderId prevents mixing IDs
  3. Validation boundaries: Email, PhoneNumber enforce validity
  4. Unit safety: Meters vs Seconds prevents unit confusion
  5. Custom serialization: Different JSON format than default
  6. API clarity: Makes function signatures more descriptive
// ❌ Unclear API:
fn process_user(id: String, email: String, name: String) { }

// ✅ Clear API with type safety:
fn process_user(id: UserId, email: Email, name: UserName) { }
// Compiler prevents: process_user(email, id, name) ✅

When NOT to Use

Don't use newtypes when:

  1. Extension traits suffice: If you control the trait, use extension traits instead
  2. Over-wrapping: Not every String needs to be wrapped
  3. Performance critical paths: Excessive wrapping/unwrapping can hurt (though usually zero-cost)
  4. Simple internal code: Don't over-engineer small scripts
  5. Standard traits available: If the foreign type already implements what you need
// ❌ Over-engineering:
struct UserName(String);
struct FirstName(String);
struct LastName(String);
struct MiddleName(String);
struct Nickname(String);
// Too many newtypes for simple data

// ✅ Reasonable:
struct Email(String);  // Needs validation
struct PhoneNumber(String);  // Needs validation
// Everything else can be String

// ❌ When extension trait works:
trait VecExt<T> {
    fn my_method(&self);
}
impl<T> VecExt<T> for Vec<T> {  // ✅ Extension trait works!
    fn my_method(&self) { }
}
// No need for: struct MyVec<T>(Vec<T>);

⚠️ Anti-patterns

1. Over-using Newtypes

// ❌ Wrapping everything:
struct MyString(String);
struct MyVec<T>(Vec<T>);
struct MyOption<T>(Option<T>);
struct MyResult<T, E>(Result<T, E>);

// These add no value unless you need custom trait impls!

// ✅ Wrap only when needed:
struct Email(String);  // Validation needed
struct UserId(uuid::Uuid);  // Custom serialization needed
// Use String, Vec, etc. directly elsewhere

2. Not Implementing Deref

// ❌ Inconvenient:
struct UserId(uuid::Uuid);

let id = UserId(uuid::Uuid::new_v4());
let version = id.0.get_version_num();  // Need .0 everywhere
let string = id.0.to_string();

// ✅ Implement Deref for convenience:
impl std::ops::Deref for UserId {
    type Target = uuid::Uuid;
    fn deref(&self) -> &Self::Target { &self.0 }
}

let id = UserId(uuid::Uuid::new_v4());
let version = id.get_version_num();  // Direct access via Deref!
let string = id.to_string();

⚠️ 3. Forgetting Common Derives

// ❌ Minimal derives:
struct UserId(uuid::Uuid);

// Can't compare, can't print, can't use in HashMap...
let id1 = UserId(uuid::Uuid::new_v4());
let id2 = id1;  // ❌ Moved! Not Copy
// if id1 == id2 { }  // ❌ No PartialEq

// ✅ Derive common traits:
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
struct UserId(uuid::Uuid);

let id1 = UserId(uuid::Uuid::new_v4());
let id2 = id1;  // ✅ Copy
if id1 == id2 { }  // ✅ PartialEq
println!("{:?}", id1);  // ✅ Debug

4. Excessive Wrapping/Unwrapping Ceremony

// ❌ Too much ceremony:
fn process_user(user_id: UserId) {
    let uuid: uuid::Uuid = user_id.0;  // Unwrap
    let result = call_external_api(uuid);
    let wrapped = UserId(result);  // Wrap again
    // Repeat many times...
}

// ✅ Use Deref and From/Into:
impl From<UserId> for uuid::Uuid {
    fn from(id: UserId) -> Self { id.0 }
}

impl From<uuid::Uuid> for UserId {
    fn from(uuid: uuid::Uuid) -> Self { UserId(uuid) }
}

fn process_user(user_id: UserId) {
    let result = call_external_api(user_id.into());  // Automatic conversion
    let wrapped: UserId = result.into();
}

5. Not Using #[repr(transparent)]

// ❌ No layout guarantee:
struct UserId(uuid::Uuid);

// ✅ Guaranteed same layout for FFI safety:
#[repr(transparent)]
struct UserId(uuid::Uuid);

// Now safe for FFI:
#[no_mangle]
extern "C" fn process_user_id(id: UserId) {
    // Can safely pass to C code expecting Uuid layout
}

Performance Characteristics

Zero-Cost Abstraction with #[repr(transparent)]

#[repr(transparent)]
struct Meters(f64);

// Memory layout is IDENTICAL to f64:
assert_eq!(
    std::mem::size_of::<Meters>(),
    std::mem::size_of::<f64>()
);

// Assembly is identical to raw f64:
pub fn add_meters(a: Meters, b: Meters) -> Meters {
    Meters(a.0 + b.0)
}

pub fn add_f64(a: f64, b: f64) -> f64 {
    a + b
}
// These compile to the SAME assembly code!

Compile-Time Guarantees Only

// All type checking happens at compile time:
let distance = Meters(100.0);
let time = Seconds(10.0);

// ❌ Compile error (zero runtime cost):
// let wrong = distance + time;

// At runtime, these are just f64 values
// No runtime type checking, no vtables, no overhead

Monomorphization Implications

// Generic newtypes cause monomorphization:
struct Wrapper<T>(T);

fn process<T>(w: Wrapper<T>) { }

process(Wrapper(42i32));     // Generates code for Wrapper<i32>
process(Wrapper(3.14f64));   // Generates code for Wrapper<f64>
process(Wrapper("hello"));   // Generates code for Wrapper<&str>

// This increases binary size but maintains zero runtime overhead
// Each instantiation is optimized for its specific type

Deref Coercion Cost

use std::ops::Deref;

struct UserId(uuid::Uuid);

impl Deref for UserId {
    type Target = uuid::Uuid;
    fn deref(&self) -> &Self::Target { &self.0 }
}

// Deref is zero-cost - just a reference:
let id = UserId(uuid::Uuid::new_v4());
let version = id.get_version_num();  // No overhead

// It's literally just:
// 1. Get reference to id.0
// 2. Call method on uuid::Uuid
// Same as: id.0.get_version_num()

When There IS Cost

// ❌ Repeated conversions can have cost:
fn bad_usage() {
    let uuid = uuid::Uuid::new_v4();
    let user_id = UserId(uuid);  // Wrap
    let uuid_again = user_id.0;  // Unwrap
    let user_id_again = UserId(uuid_again);  // Wrap again
    // While individually cheap, this pattern in hot loops matters
}

// ✅ Keep it wrapped:
fn good_usage() {
    let user_id = UserId::new();
    // Use user_id directly throughout
    process(&user_id);  // Pass by reference, zero cost
}

// ❌ Cloning large inner types:
struct BigData(Vec<u8>);  // Vec clones are expensive

fn process(data: BigData) {
    let clone1 = data.clone();  // Expensive!
    let clone2 = data.clone();  // Expensive!
}

// ✅ Use references:
fn process(data: &BigData) {
    // Work with reference, no cloning needed
}

Exercises

Beginner: Email Validation Newtype

Create an Email newtype that validates email addresses and implements common traits.

// TODO: Implement Email newtype with:
// - Validation in constructor
// - FromStr trait
// - Display trait
// - AsRef<str> trait
// - PartialEq, Eq, Hash derives

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn valid_email() {
        let email = Email::new("user@example.com".to_string()).unwrap();
        assert_eq!(email.as_ref(), "user@example.com");
    }
    
    #[test]
    fn invalid_email() {
        assert!(Email::new("not-an-email".to_string()).is_err());
    }
    
    #[test]
    fn domain_extraction() {
        let email = Email::new("user@example.com".to_string()).unwrap();
        assert_eq!(email.domain(), "example.com");
    }
}

Intermediate: Type-Safe Unit System

Build a complete unit system with Distance, Time, and Velocity types that prevent invalid operations.

// TODO: Implement type-safe units:
// - Meters and Kilometers (with conversions)
// - Seconds and Hours (with conversions)
// - MetersPerSecond (velocity)
// - Arithmetic: distance + distance, distance / time = velocity
// - Prevent: distance + time (compile error)

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn distance_arithmetic() {
        let d1 = Meters::new(100.0);
        let d2 = Meters::new(50.0);
        let total = d1 + d2;
        assert_eq!(total.value(), 150.0);
    }
    
    #[test]
    fn unit_conversion() {
        let meters = Meters::new(5000.0);
        let km = meters.to_kilometers();
        assert_eq!(km.value(), 5.0);
    }
    
    #[test]
    fn velocity_calculation() {
        let distance = Meters::new(100.0);
        let time = Seconds::new(10.0);
        let velocity = distance / time;
        
        let new_distance = velocity * Seconds::new(5.0);
        assert_eq!(new_distance.value(), 50.0);
    }
    
    // This should NOT compile:
    // #[test]
    // fn invalid_addition() {
    //     let distance = Meters::new(100.0);
    //     let time = Seconds::new(10.0);
    //     let wrong = distance + time;  // ❌ Type error!
    // }
}

Advanced: API Client with Validated Types

Build a REST API client using newtypes for all request/response types to ensure validation at boundaries.

// TODO: Create an API client with:
// - UserId, OrderId, ProductId newtypes (UUID-based)
// - Email, PhoneNumber validation newtypes
// - Price newtype with currency validation
// - Custom serialization for all types
// - Type-safe request/response structs

use serde::{Serialize, Deserialize};
use uuid::Uuid;

// Example structure:
#[derive(Debug, Serialize, Deserialize)]
struct CreateUserRequest {
    email: Email,
    phone: PhoneNumber,
    // Add more validated fields
}

#[derive(Debug, Serialize, Deserialize)]
struct CreateUserResponse {
    user_id: UserId,
    email: Email,
    created_at: Timestamp,
}

#[derive(Debug, Serialize, Deserialize)]
struct CreateOrderRequest {
    user_id: UserId,
    product_id: ProductId,
    quantity: Quantity,  // Newtype with positive validation
    price: Price,        // Newtype with currency
}

// TODO: Implement ApiClient with methods:
// - create_user(request: CreateUserRequest) -> Result<CreateUserResponse, Error>
// - create_order(request: CreateOrderRequest) -> Result<CreateOrderResponse, Error>
// - get_user(id: UserId) -> Result<User, Error>

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn create_user_flow() {
        let request = CreateUserRequest {
            email: Email::new("user@example.com".to_string()).unwrap(),
            phone: PhoneNumber::new("555-123-4567".to_string()).unwrap(),
        };
        
        // API call would happen here
        // let response = client.create_user(request).await.unwrap();
        // assert!(response.user_id is valid UUID);
    }
    
    #[test]
    fn invalid_email_rejected() {
        let result = Email::new("invalid".to_string());
        assert!(result.is_err());
        // Invalid data never reaches the API
    }
    
    #[test]
    fn type_safety_prevents_id_confusion() {
        let user_id = UserId::new();
        let order_id = OrderId::new();
        
        // This should NOT compile:
        // assert_eq!(user_id, order_id);  // ❌ Different types!
        
        // This prevents bugs like:
        // get_user(order_id)  // ❌ Type error prevents this mistake
    }
}

Real-World Usage

Standard Library Examples

// String vs &str is a form of newtype pattern
// String owns, &str borrows, but same underlying data

// PathBuf vs Path
use std::path::{Path, PathBuf};
let path = PathBuf::from("/usr/local/bin");  // Owned
let path_ref: &Path = &path;  // Borrowed

// OsString vs OsStr
use std::ffi::{OsString, OsStr};
let os_string = OsString::from("filename");

// CString vs CStr (for FFI)
use std::ffi::{CString, CStr};
let c_string = CString::new("hello").unwrap();

Diesel ORM Type Wrappers

// Diesel uses newtypes for SQL types:
use diesel::sql_types::*;
use diesel::deserialize::{self, FromSql};
use diesel::serialize::{self, ToSql};

// Example from diesel documentation:
#[derive(Debug, Clone, Copy, AsExpression, FromSqlRow)]
#[diesel(sql_type = diesel::sql_types::Timestamp)]
struct MyTimestamp(chrono::NaiveDateTime);

impl FromSql<Timestamp, Pg> for MyTimestamp {
    fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
        let naive = <chrono::NaiveDateTime as FromSql<Timestamp, Pg>>::from_sql(bytes)?;
        Ok(MyTimestamp(naive))
    }
}

impl ToSql<Timestamp, Pg> for MyTimestamp {
    fn to_sql<W: std::io::Write>(&self, out: &mut serialize::Output<W, Pg>) 
        -> serialize::Result 
    {
        <chrono::NaiveDateTime as ToSql<Timestamp, Pg>>::to_sql(&self.0, out)
    }
}

Actix-Web Extractors

// Actix uses newtypes for type-safe extractors:
use actix_web::{web, App, HttpResponse, HttpServer};

// Newtype for path parameters
#[derive(Debug, Deserialize)]
struct UserId(uuid::Uuid);

// Newtype for query parameters  
#[derive(Debug, Deserialize)]
struct Pagination {
    page: u32,
    per_page: u32,
}

async fn get_user(
    user_id: web::Path<UserId>,  // Type-safe path extraction
    pagination: web::Query<Pagination>,  // Type-safe query extraction
) -> HttpResponse {
    HttpResponse::Ok().json(format!(
        "User {} (page {}, per_page {})",
        user_id.0, pagination.page, pagination.per_page
    ))
}

Serde JSON Extensions

// Extend serde_json::Value with custom behavior:
use serde_json::Value;

trait JsonValueExt {
    fn get_string(&self, key: &str) -> Option<&str>;
    fn get_number(&self, key: &str) -> Option<f64>;
}

impl JsonValueExt for Value {
    fn get_string(&self, key: &str) -> Option<&str> {
        self.get(key)?.as_str()
    }
    
    fn get_number(&self, key: &str) -> Option<f64> {
        self.get(key)?.as_f64()
    }
}

// Extension trait works here - no need for newtype!
// But if you needed to implement foreign traits (like Display),
// you'd need a newtype:

struct JsonValue(serde_json::Value);

impl std::fmt::Display for JsonValue {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", serde_json::to_string_pretty(&self.0).unwrap())
    }
}

Time/Chrono Wrappers

// Common pattern: wrap chrono types for custom serialization
use chrono::{DateTime, Utc};
use serde::{Serialize, Deserialize, Serializer, Deserializer};

#[derive(Debug, Clone, Copy)]
struct Timestamp(DateTime<Utc>);

impl Serialize for Timestamp {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        // Serialize as Unix timestamp instead of RFC3339
        serializer.serialize_i64(self.0.timestamp())
    }
}

impl<'de> Deserialize<'de> for Timestamp {
    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        let timestamp = i64::deserialize(deserializer)?;
        let dt = DateTime::from_timestamp(timestamp, 0)
            .ok_or_else(|| serde::de::Error::custom("invalid timestamp"))?;
        Ok(Timestamp(dt))
    }
}

// Now JSON serializes as number instead of string:
// {"created_at": 1699564800} instead of {"created_at": "2023-11-10T00:00:00Z"}

Further Reading

---

The orphan rule and newtype pattern demonstrate Rust's commitment to preventing conflicts at compile time. While the orphan rule can feel restrictive, it guarantees that your code will never have ambiguous trait implementations. The newtype pattern elegantly works within these constraints while providing additional benefits like semantic type safety and zero-cost abstractions. Understanding when and how to use newtypes is essential for building large Rust applications where type safety and clarity matter.

🎮 Try it Yourself

🎮

Orphan Rules & Newtype - Playground

Run this code in the official Rust Playground