Skip to content

Advanced Types ​

Updated Mar 2026

Type aliases, the never type (!), dynamically sized types, function pointers, closures as return types, and PhantomData for zero-cost type-level programming.

Type Aliases ​

Create a new name for an existing type:

rust
type Kilometers = i32;

let distance: Kilometers = 5;
let speed: i32 = 10;
// These are the same type -- fully interchangeable
let time = distance / speed;

Reducing Repetition ​

Type aliases shine when the original type is long:

rust
// Before: verbose
fn takes_long_type(f: Box<dyn Fn() + Send + 'static>) { /* ... */ }
fn returns_long_type() -> Box<dyn Fn() + Send + 'static> { /* ... */ }

// After: clean
type Thunk = Box<dyn Fn() + Send + 'static>;
fn takes_thunk(f: Thunk) { /* ... */ }
fn returns_thunk() -> Thunk { /* ... */ }

The std::io::Result Pattern ​

The standard library uses type aliases extensively:

rust
// In std::io:
type Result<T> = std::result::Result<T, std::io::Error>;

// Now io functions use the short form:
pub fn read_to_string(&mut self, buf: &mut String) -> Result<usize> { /* ... */ }
// Instead of: -> std::result::Result<usize, std::io::Error>

Type Alias vs Newtype ​

FeatureType AliasNewtype
Creates new typeNo (same type)Yes (distinct type)
Type checkingInterchangeablePrevents mixing
Method accessAll methods availableMust impl Deref or wrap
Use caseReducing verbosityType safety
rust
type Meters = f64;
type Seconds = f64;
let m: Meters = 5.0;
let s: Seconds = 3.0;
let _ = m + s;  // compiles -- they're both f64

struct MetersNT(f64);
struct SecondsNT(f64);
// MetersNT(5.0) + SecondsNT(3.0)  // won't compile -- different types

The Never Type (!) ​

The ! type represents computations that never produce a value:

rust
// Functions that never return
fn diverges() -> ! {
    panic!("This function never returns");
}

fn infinite_loop() -> ! {
    loop {
        // runs forever
    }
}

Why ! Exists ​

! can be coerced into any type, which makes control flow expressions work:

rust
// continue has type ! -- coerces to u32 in this context
let guess: u32 = match input.trim().parse() {
    Ok(num) => num,
    Err(_) => continue,  // ! coerces to u32
};

// panic! has type ! -- coerces to T in unwrap
impl<T> Option<T> {
    pub fn unwrap(self) -> T {
        match self {
            Some(val) => val,
            None => panic!("called unwrap on None"),  // ! coerces to T
        }
    }
}

// Infinite loops have type !
let value: i32 = loop {
    break 42;  // breaks with i32 value
};

Expressions That Return ! ​

ExpressionTypeWhy
panic!(...)!Terminates the thread
loop { ... } (no break)!Runs forever
continue!Jumps to loop start
return!Exits function
std::process::exit(0)!Terminates process
unreachable!()!UB hint to compiler

Dynamically Sized Types (DSTs) ​

Most types have a known size at compile time. DSTs do not:

rust
// str is a DST -- its length isn't known at compile time
// You can't use str directly:
// let s: str = "hello";  // ERROR: size not known

// Must use behind a pointer:
let s: &str = "hello";           // fat pointer: (ptr, length)
let s: Box<str> = "hello".into(); // heap-allocated DST

The Golden Rule ​

DST values must always live behind some kind of pointer.

Common pointer types for DSTs:

PointerUse Case
&TBorrowed reference
&mut TMutable reference
Box<T>Owned heap allocation
Rc<T>Reference-counted
Arc<T>Atomically reference-counted

Fat Pointers ​

References to DSTs are "fat pointers" -- twice the size of normal pointers:

rust
use std::mem::size_of;

// Regular pointer: just an address
assert_eq!(size_of::<&u32>(), 8);       // 8 bytes on 64-bit

// Fat pointer to str: address + length
assert_eq!(size_of::<&str>(), 16);      // 16 bytes on 64-bit

// Fat pointer to dyn Trait: address + vtable pointer
assert_eq!(size_of::<&dyn ToString>(), 16);  // 16 bytes

The Sized Trait ​

Every generic type parameter has an implicit Sized bound:

rust
// These are equivalent:
fn generic<T>(t: T) { }
fn generic<T: Sized>(t: T) { }

Use ?Sized to opt out and accept DSTs:

rust
// T can be a DST, but must be behind a reference
fn generic<T: ?Sized>(t: &T) {
    // t is a fat pointer if T is unsized
}

// Now works with both:
generic(&42_u32);     // T = u32 (Sized)
generic("hello");     // T = str (not Sized, passed as &str)

Common DSTs ​

DSTSized EquivalentFat Pointer
strString&str (ptr + len)
[T]Vec<T>&[T] (ptr + len)
dyn TraitBox<dyn Trait>&dyn Trait (ptr + vtable)
PathPathBuf&Path (ptr + len)
OsStrOsString&OsStr (ptr + len)
CStrCString&CStr (ptr + len)

Function Pointers ​

The fn type (lowercase) represents a function pointer:

rust
fn add_one(x: i32) -> i32 { x + 1 }

// fn is a type, not a trait
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
    f(arg) + f(arg)
}

let answer = do_twice(add_one, 5);
assert_eq!(answer, 12);

fn vs Fn ​

Featurefn (type)Fn (trait)
What is itConcrete function pointerTrait bound
Accepts closuresOnly non-capturingAll closures
SizeAlways one pointerVaries (captures)
FFI compatibleYesNo
rust
// fn: only accepts function pointers and non-capturing closures
fn takes_fn_pointer(f: fn(i32) -> i32) { }
takes_fn_pointer(add_one);     // OK
takes_fn_pointer(|x| x + 1);  // OK (non-capturing)

// Fn: accepts any callable
fn takes_fn_trait(f: impl Fn(i32) -> i32) { }
takes_fn_trait(add_one);       // OK
takes_fn_trait(|x| x + 1);    // OK
let offset = 5;
takes_fn_trait(|x| x + offset); // OK (capturing closure)

Enum Variants as Function Pointers ​

Enum variant constructors are function pointers:

rust
enum Status {
    Value(u32),
    Stop,
}

// Status::Value is fn(u32) -> Status
let list_of_statuses: Vec<Status> =
    (0u32..20).map(Status::Value).collect();

Closures as Return Types ​

Returning a Single Closure Type ​

rust
fn returns_closure() -> impl Fn(i32) -> i32 {
    |x| x + 1
}

Returning Different Closures ​

Each closure has a unique anonymous type, so you can't return different closures with impl Fn:

rust
// ERROR: different closures have different types
// fn returns_either(flag: bool) -> impl Fn(i32) -> i32 {
//     if flag { |x| x + 1 } else { |x| x - 1 }
// }

// FIX: use trait objects
fn returns_either(flag: bool) -> Box<dyn Fn(i32) -> i32> {
    if flag {
        Box::new(|x| x + 1)
    } else {
        Box::new(|x| x - 1)
    }
}

PhantomData -- Zero-Cost Type Markers ​

PhantomData<T> is a zero-sized type that tells the compiler "this struct logically owns a T":

rust
use std::marker::PhantomData;

// Without PhantomData: compiler doesn't know Iter borrows from 'a
struct Iter<'a, T> {
    ptr: *const T,
    end: *const T,
    _marker: PhantomData<&'a T>,  // "I logically hold &'a T"
}

// PhantomData affects:
// - Lifetime checking (struct borrows from 'a)
// - Drop checking (T might need dropping)
// - Variance (covariant in T for PhantomData<T>)
// - Auto traits (Send/Sync depend on T)

Common PhantomData Patterns ​

rust
// Mark ownership of T (affects drop check)
struct MyVec<T> {
    ptr: *mut T,
    len: usize,
    cap: usize,
    _marker: PhantomData<T>,  // owns T values
}

// Mark lifetime relationship
struct Ref<'a, T: 'a> {
    ptr: *const T,
    _marker: PhantomData<&'a T>,  // borrows T for 'a
}

// Type-level state machine (typestate pattern)
struct Locked;
struct Unlocked;

struct Door<State> {
    _state: PhantomData<State>,
}

impl Door<Locked> {
    fn unlock(self) -> Door<Unlocked> {
        Door { _state: PhantomData }
    }
}

impl Door<Unlocked> {
    fn lock(self) -> Door<Locked> {
        Door { _state: PhantomData }
    }

    fn open(&self) {
        println!("Door is open");
    }
}

let door = Door::<Locked> { _state: PhantomData };
// door.open();      // ERROR: no method open() on Door<Locked>
let door = door.unlock();
door.open();          // OK: Door<Unlocked> has open()

Quick Reference ​

Type FeatureSyntaxPurpose
Type aliastype Name = Existing;Reduce verbosity
Never type-> !Functions that don't return
DST boundT: ?SizedAccept dynamically sized types
Function pointerfn(i32) -> i32FFI-compatible callable
Closure traitimpl Fn(i32) -> i32Any callable
Boxed closureBox<dyn Fn(i32) -> i32>Heterogeneous callables
PhantomDataPhantomData<T>Zero-cost type marker

Built with VitePress | Rust SOP Documentation