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
#[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 = ;
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 = ;
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 = ;
let port = Port::new(raw).unwrap_or(Port(80));
println!("port={}", port.0);
}
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.