Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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);
}