🎓 A1Learner
🔍

Rust Validated Types

A validated type is a type whose values are checked once, at creation. A Guess value, for example, always holds a number from 1 to 100.

The long way: the same check in every function

With a plain i32, every function that needs the promise repeats the range check:

let guess = 42;
if guess < 1 || guess > 100 {
    println!("out of range");
} else {
    println!("describe: {guess} is in range");
}
if guess < 1 || guess > 100 {
    println!("out of range");
} else {
    println!("score: {} points", guess * 2);
}

A private field guards the value

No pub on the field, so outside code cannot set the value directly.

struct Guess {
    value: i32, // private: no code outside can set it directly
}

One maker checks before building

new is the only door in, and the check lives behind that door.

fn new(value: i32) -> Guess {
    if value < 1 || value > 100 {
        panic!("Guess value must be between 1 and 100, got {}.", value);
    }
    Guess { value }
}

A getter reads the value back

The field stays private, so a small method hands the value out.

let guess = Guess::new(42);
println!("the guess is {}", guess.value());

Or refuse with a Result

A panic suits a caller bug. When bad input is expected instead, the maker can return a Result.

fn try_new(value: i32) -> Result<Guess, String> {
    if (1..=100).contains(&value) {
        Ok(Guess { value })
    } else {
        Err(format!("{value} is outside 1 to 100"))
    }
}

Other functions never check again

A function that takes a Guess trusts it completely — the check already happened at the door.

fn describe(guess: &Guess) {
    // no range check here: a Guess is in range by construction
    println!("guess {} is in range", guess.value());
}

Example

struct Guess { value: i32 }
impl Guess {
    fn new(value: i32) -> Guess {
        if value < 1 || value > 100 {
            panic!("Guess value must be between 1 and 100, got {}.", value);
        }
        Guess { value }
    }
    fn value(&self) -> i32 { self.value }
}
fn main() {
    let guess = Guess::new(42);
    println!("the guess is {}", guess.value());
}

Key points

  • The same idea sits behind plain types.
  • A u32 parameter can never be negative.
  • A String parameter is never missing.

In this example, Guess holds one i32 in a private field. The only way to build a Guess is Guess::new. new checks the range first. 42 passes, so a Guess is created. main reads the number back through the value() getter and prints the guess is 42. A call like Guess::new(500) would panic with the range message instead of creating a bad value. From now on, any function that receives a Guess can trust it completely — the check happened once, at the door.

Note

Document the maker's panic in its docs, so callers know the contract before they break it.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

Why make the field of a validated type private?