Newtype Pattern

A newtype is a wrapper around an existing type, often a primitive:

#![allow(unused)]
fn main() {
/// A unique user identifier, implemented as a newtype around `u64`.
pub struct UserId(u64);
}

Unlike type aliases, newtypes aren’t interchangeable with the wrapped type:

#![allow(unused)]
fn main() {
pub struct UserId(u64);
fn double(n: u64) -> u64 {
    n * 2
}

double(UserId(1)); // 🛠️❌
}

The Rust compiler won’t let you use methods or operators defined on the underlying type either:

#![allow(unused)]
fn main() {
pub struct UserId(u64);
assert_ne!(UserId(1), UserId(2)); // 🛠️❌
}
This slide and its sub-slides should take about 20 minutes.
  • Students should have encountered the newtype pattern in the “Fundamentals” course, when they learned about tuple structs.

  • Run the example to show students the error message from the compiler.

  • Modify the example to use a typealias instead of a newtype, such as type MessageId = u64. The modified example should compile, thus highlighting the differences between the two approaches.

  • Stress that newtypes, out of the box, have no behaviour attached to them. You need to be intentional about which methods and operators you are willing to forward from the underlying type. In our UserId example, it is reasonable to allow comparisons between UserIds, but it wouldn’t make sense to allow arithmetic operations like addition or subtraction.