bypassable-constructor
Level: warn · Article: Primitives
The invariant lives in the type. It cannot be violated without going through the constructor. The constructor rejects violations.
What it checks
A single-field tuple struct with a pub field, in the same file as an
associated function that returns Result<Self, _> or Option<Self>. The
constructor can say no; the pub field lets anyone build the value without
asking.
Don’t
#![allow(unused)]
fn main() {
pub struct Percentage(pub f64);
impl Percentage {
pub fn new(n: f64) -> Result<Self, ValidationError> {
if !(0.0..=100.0).contains(&n) {
return Err(ValidationError::OutOfRange(n));
}
Ok(Self(n))
}
}
impl Cart {
fn apply_discount(&mut self) {
self.discount = Percentage(250.0); // never went through the door
}
}
}
Do
#![allow(unused)]
fn main() {
pub struct Percentage(f64);
impl Percentage {
pub fn new(n: f64) -> Result<Self, ValidationError> {
if !(0.0..=100.0).contains(&n) {
return Err(ValidationError::OutOfRange(n));
}
Ok(Self(n))
}
pub fn value(&self) -> f64 {
self.0
}
}
}
One way in. Every Percentage in the program has been checked, by
construction, and no function that receives one has to check again.
Silence it
#![allow(unused)]
fn main() {
// rabot: allow(bypassable-constructor) any f64 is a valid Meters; `parse` only exists for the text form
pub struct Meters(pub f64);
}