Newtypes and extension traits
cargo run --bin 28_newtype_extensionstruct Watts(u32) is a new type, not an alias. You cannot pass Watts where a u32 or a Volts is expected, so units and meanings stay straight. The wrapper costs nothing at runtime. A newtype can also enforce an invariant (a validated email, a non-empty list) by keeping its field private and checking in the constructor.
The orphan rule says you may implement a trait for a type only if you own the trait or the type. A newtype gets around it: wrap the foreign type, then implement the foreign trait on your wrapper.
An extension trait goes the other way: define your own trait and implement it for a type you do not own, to add methods to it. The methods are visible wherever the trait is in scope.
Newtypes keep units apart
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
struct Watts(u32);
#[derive(Debug, Clone, Copy)]
struct Volts(u32);
impl fmt::Display for Watts {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}W", self.0)
}
}
fn draw(power: Watts) -> String {
format!("drawing {power}")
}
fn demo_units() {
println!("-- units --");
println!("{}", draw(Watts(60)));
let v = Volts(230);
// draw(v); // error[E0308]: expected `Watts`, found `Volts`
// draw(60); // error[E0308]: expected `Watts`, found integer
println!("volts {}", v.0);
println!("zero cost: {} bytes", size_of::<Watts>());
}A newtype that guarantees an invariant
mod email {
#[derive(Debug, Clone)]
pub struct Email(String);
impl Email {
pub fn parse(s: &str) -> Result<Self, String> {
match s.split_once('@') {
Some((user, domain)) if !user.is_empty() && domain.contains('.') => {
Ok(Email(s.to_lowercase()))
}
_ => Err(format!("{s:?} is not an email")),
}
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn domain(&self) -> &str {
self.0.split_once('@').map(|(_, d)| d).unwrap_or("")
}
}
}
fn demo_validated() {
println!("-- validated newtype --");
for s in ["[email protected]", "nope", "@x.io"] {
match email::Email::parse(s) {
Ok(e) => println!("ok {} domain {}", e.as_str(), e.domain()),
Err(msg) => println!("{msg}"),
}
}
// email::Email("bad".into()); // error[E0603]: tuple struct constructor `Email` is private
}The newtype gets around the orphan rule
// impl fmt::Display for Vec<String> {}
// error[E0117]: only traits defined in the current crate can be implemented for types
// defined outside of the crate
struct List(Vec<String>);
impl fmt::Display for List {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{}]", self.0.join(", "))
}
}
fn demo_orphan() {
println!("-- orphan rule --");
let l = List(vec!["a".into(), "b".into()]);
println!("{l} has {} items", l.0.len());
}Exposing the inner value
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
struct UserId(u64);
impl From<u64> for UserId {
fn from(n: u64) -> Self {
UserId(n)
}
}
impl UserId {
fn get(self) -> u64 {
self.0
}
}
fn demo_inner_access() {
println!("-- inner access --");
let id: UserId = 42.into();
let UserId(raw) = id;
println!("{id:?} get {} destructured {raw}", id.get());
let mut ids = vec![UserId(3), UserId(1), UserId(2)];
ids.sort();
println!("derived Ord works: {ids:?}");
}Extension traits add methods to foreign types
trait Hex {
fn hex(&self) -> String;
}
impl Hex for u8 {
fn hex(&self) -> String {
format!("{self:02x}")
}
}
impl Hex for [u8] {
fn hex(&self) -> String {
self.iter().map(|b| b.hex()).collect()
}
}
fn demo_extension() {
println!("-- extension traits --");
println!("{} {}", 15u8.hex(), b"Rust"[..].hex());
}Blanket extension traits
trait StrExt {
fn word_count(&self) -> usize;
fn title_case(&self) -> String;
}
impl StrExt for str {
fn word_count(&self) -> usize {
self.split_whitespace().count()
}
fn title_case(&self) -> String {
self.split_whitespace()
.map(|w| {
let mut c = w.chars();
match c.next() {
Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
None => String::new(),
}
})
.collect::<Vec<_>>()
.join(" ")
}
}
trait IterExt: Iterator {
fn sum_squares(self) -> i64
where
Self: Sized,
Self::Item: Into<i64>,
{
self.map(|x| {
let x: i64 = x.into();
x * x
})
.sum()
}
}
// A blanket impl: every iterator gets the method.
impl<I: Iterator> IterExt for I {}
fn demo_blanket_extension() {
println!("-- blanket extension --");
let s = "the quick brown fox";
println!("{} words, {}", s.word_count(), s.title_case());
println!("String derefs to str: {}", String::from("a b").word_count());
println!("sum of squares {}", [1, 2, 3].into_iter().sum_squares());
println!("over a range {}", (1u8..=4).sum_squares());
}$ cargo run --bin 28_newtype_extension -- units -- drawing 60W volts 230 zero cost: 4 bytes -- validated newtype -- ok [email protected] domain example.com "nope" is not an email "@x.io" is not an email -- orphan rule -- [a, b] has 2 items -- inner access -- UserId(42) get 42 destructured 42 derived Ord works: [UserId(1), UserId(2), UserId(3)] -- extension traits -- 0f 52757374 -- blanket extension -- 4 words, The Quick Brown Fox String derefs to str: 2 sum of squares 14 over a range 30
Full program
// 28. Newtypes and extension traits
//
// struct Watts(u32) is a new type, not an alias. You cannot pass Watts where a u32 or a
// Volts is expected, so units and meanings stay straight. The wrapper costs nothing at
// runtime. A newtype can also enforce an invariant (a validated email, a non-empty
// list) by keeping its field private and checking in the constructor.
//
// The orphan rule says you may implement a trait for a type only if you own the trait
// or the type. A newtype gets around it: wrap the foreign type, then implement the
// foreign trait on your wrapper.
//
// An extension trait goes the other way: define your own trait and implement it for a
// type you do not own, to add methods to it. The methods are visible wherever the trait
// is in scope.
//
// Run: cargo run --bin 28_newtype_extension
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
struct Watts(u32);
#[derive(Debug, Clone, Copy)]
struct Volts(u32);
impl fmt::Display for Watts {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}W", self.0)
}
}
fn draw(power: Watts) -> String {
format!("drawing {power}")
}
// Example 1: newtypes keep units apart.
fn demo_units() {
println!("-- units --");
println!("{}", draw(Watts(60)));
let v = Volts(230);
// draw(v); // error[E0308]: expected `Watts`, found `Volts`
// draw(60); // error[E0308]: expected `Watts`, found integer
println!("volts {}", v.0);
println!("zero cost: {} bytes", size_of::<Watts>());
}
mod email {
#[derive(Debug, Clone)]
pub struct Email(String);
impl Email {
pub fn parse(s: &str) -> Result<Self, String> {
match s.split_once('@') {
Some((user, domain)) if !user.is_empty() && domain.contains('.') => {
Ok(Email(s.to_lowercase()))
}
_ => Err(format!("{s:?} is not an email")),
}
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn domain(&self) -> &str {
self.0.split_once('@').map(|(_, d)| d).unwrap_or("")
}
}
}
// Example 2: a newtype that guarantees an invariant.
fn demo_validated() {
println!("-- validated newtype --");
for s in ["[email protected]", "nope", "@x.io"] {
match email::Email::parse(s) {
Ok(e) => println!("ok {} domain {}", e.as_str(), e.domain()),
Err(msg) => println!("{msg}"),
}
}
// email::Email("bad".into()); // error[E0603]: tuple struct constructor `Email` is private
}
// impl fmt::Display for Vec<String> {}
// error[E0117]: only traits defined in the current crate can be implemented for types
// defined outside of the crate
struct List(Vec<String>);
impl fmt::Display for List {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{}]", self.0.join(", "))
}
}
// Example 3: the newtype gets around the orphan rule.
fn demo_orphan() {
println!("-- orphan rule --");
let l = List(vec!["a".into(), "b".into()]);
println!("{l} has {} items", l.0.len());
}
// Example 4: exposing the inner value.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
struct UserId(u64);
impl From<u64> for UserId {
fn from(n: u64) -> Self {
UserId(n)
}
}
impl UserId {
fn get(self) -> u64 {
self.0
}
}
fn demo_inner_access() {
println!("-- inner access --");
let id: UserId = 42.into();
let UserId(raw) = id;
println!("{id:?} get {} destructured {raw}", id.get());
let mut ids = vec![UserId(3), UserId(1), UserId(2)];
ids.sort();
println!("derived Ord works: {ids:?}");
}
trait Hex {
fn hex(&self) -> String;
}
impl Hex for u8 {
fn hex(&self) -> String {
format!("{self:02x}")
}
}
impl Hex for [u8] {
fn hex(&self) -> String {
self.iter().map(|b| b.hex()).collect()
}
}
// Example 5: extension traits add methods to foreign types.
fn demo_extension() {
println!("-- extension traits --");
println!("{} {}", 15u8.hex(), b"Rust"[..].hex());
}
trait StrExt {
fn word_count(&self) -> usize;
fn title_case(&self) -> String;
}
impl StrExt for str {
fn word_count(&self) -> usize {
self.split_whitespace().count()
}
fn title_case(&self) -> String {
self.split_whitespace()
.map(|w| {
let mut c = w.chars();
match c.next() {
Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
None => String::new(),
}
})
.collect::<Vec<_>>()
.join(" ")
}
}
trait IterExt: Iterator {
fn sum_squares(self) -> i64
where
Self: Sized,
Self::Item: Into<i64>,
{
self.map(|x| {
let x: i64 = x.into();
x * x
})
.sum()
}
}
// A blanket impl: every iterator gets the method.
impl<I: Iterator> IterExt for I {}
// Example 6: blanket extension traits.
fn demo_blanket_extension() {
println!("-- blanket extension --");
let s = "the quick brown fox";
println!("{} words, {}", s.word_count(), s.title_case());
println!("String derefs to str: {}", String::from("a b").word_count());
println!("sum of squares {}", [1, 2, 3].into_iter().sum_squares());
println!("over a range {}", (1u8..=4).sum_squares());
}
fn main() {
demo_units();
demo_validated();
demo_orphan();
demo_inner_access();
demo_extension();
demo_blanket_extension();
}