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)); // 🛠️❌ }
-
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 betweenUserId
s, but it wouldn’t make sense to allow arithmetic operations like addition or subtraction.