API Design Patterns
Newtype API
Validate at the Boundary
A newtype can make invalid raw values hard to pass around by validating them once at construction.
Program
Play the program to choose a raw port and fall back when validation rejects it.
newtype_port_validation.rs
Replay: real traced execution (multi-file project)
#[derive(Clone, Copy)]
struct Port(u16);
impl Port {
fn new(value: u32) -> Option<Self> {
if (1..=65535).contains(&value) {
Some(Self(value as u16))
} else {
None
}
}
}
fn main() {
let raw = 8080;
let port = Port::new(raw).unwrap_or(Port(80));
println!("port={}", port.0);
}
#[derive(Clone, Copy)]
struct Port(u16);
impl Port {
fn new(value: u32) -> Option<Self> {
if (1..=65535).contains(&value) {
Some(Self(value as u16))
} else {
None
}
}
}
fn main() {
let raw = 0;
let port = Port::new(raw).unwrap_or(Port(80));
println!("port={}", port.0);
}
#[derive(Clone, Copy)]
struct Port(u16);
impl Port {
fn new(value: u32) -> Option<Self> {
if (1..=65535).contains(&value) {
Some(Self(value as u16))
} else {
None
}
}
}
fn main() {
let raw = 70000;
let port = Port::new(raw).unwrap_or(Port(80));
println!("port={}", port.0);
}
raw ← 8080
14fn main() {15 let ra→ 8080w = 8080; //@raw=8080, 0, 7000016 let port = Port::new(ra8080w).unwrap_or(Port(80));17 println!("port={}", port.0);port ← (empty)
15 let raw = 8080; //@raw=8080, 0, 7000016 let por→ (empty)t = Port::new(ra8080w).unwrap_or(Port(80));17 println!("port={}", port.0);18}outputport=8080
raw ← 0
14fn main() {15 let ra→ 0w = 0;16 let port = Port::new(ra0w).unwrap_or(Port(80));17 println!("port={}", port.0);port ← (empty)
15 let raw = 0;16 let por→ (empty)t = Port::new(ra0w).unwrap_or(Port(80));17 println!("port={}", port.0);18}outputport=80
raw ← 70000
14fn main() {15 let ra→ 70000w = 70000;16 let port = Port::new(ra70000w).unwrap_or(Port(80));17 println!("port={}", port.0);port ← (empty)
15 let raw = 70000;16 let por→ (empty)t = Port::new(ra70000w).unwrap_or(Port(80));17 println!("port={}", port.0);18}outputport=80
newtype
`Port` wraps `u16` so APIs can ask for a validated port instead of a raw number.
constructor
`Port::new` returns `Option<Port>` to make invalid input explicit.
fallback
`unwrap_or` chooses a default only after validation has failed.