Advanced Types

Newtype Pattern for Type Safety

Compile-time type safety and abstraction:

struct UserId(u32);
struct ProductId(u32);

fn get_user(id: UserId) -> User { /* ... */ }
// get_user(ProductId(123)); // Compile error

Use cases: Units (Kilometers, Miles), IDs, domain modeling, API abstraction.

Type Aliases

Convenience syntax for complex types:

type Kilometers = i32;  // No type safety
type Thunk = Box<dyn Fn() + Send + 'static>;

// Result alias pattern (std::io uses this)
type Result<T> = std::result::Result<T, std::io::Error>;

fn write_data() -> Result<()> { /* ... */ }

Never Type (!)

Represents computations that never return:

fn never_returns() -> ! {
    panic!("This function never returns!");
}

// Key property: ! coerces to any type
let guess: u32 = match guess.trim().parse() {
    Ok(num) => num,
    Err(_) => continue,  // continue has type !, coerces to u32
};

Other ! expressions: panic!, loop {}, process::exit().

Dynamically Sized Types (DSTs)

Types with unknown compile-time size:

// str is a DST (not &str)
let s1: &str = "hello";      // OK: reference with size info
let s2: Box<str> = "hello".into();  // OK: smart pointer

// Won't compile - unknown size
// let s3: str = "hello";

Common DSTs: str, [T], dyn Trait.

Sized Trait

Generic functions implicitly require Sized:

fn generic<T>(t: T) {}           // Actually: fn generic<T: Sized>(t: T)
fn flexible<T: ?Sized>(t: &T) {} // ?Sized relaxes the bound

?Sized means “maybe sized” - requires using T behind a pointer (&T, Box<T>).