How to validate struct creation?

Viewed 630

I'm working through Rust by Example. I'm currently at TryFrom and TryInto. This is their code:

struct EvenNumber(i32);

impl TryFrom<i32> for EvenNumber {
    type Error = ();

    fn try_from(value: i32) -> Result<Self, Self::Error> {
        if value % 2 == 0 {
            Ok(EvenNumber(value))
        } else {
            Err(())
        }
    }
}

The implementation prevents the creation of an instance of EvenNumber with an odd number, but only if I call try_from (or try_into). I can still create an EvenNumber(1337) directly.

In Java (and related languages), one would validate instance creation inside a constructor, e.g.:

class EvenNumber {
    public final int value;

    public EvenNumber(int value) {
        if (value % 2 != 0) {
            throw new IllegalArgumentException("value is odd");
        }
        this.value = value;
    }
}

How to do this in Rust?

2 Answers

You'd define EvenNumber inside some module and keep its num field private, so the only way people can create instances of EvenNumber would be through a public method you control, like new. Example:

mod some_mod {
    pub struct EvenNumber {
        num: i32,
    }
    
    impl EvenNumber {
        pub fn new(num: i32) -> Result<Self, ()> {
            if num % 2 == 0 {
                Ok(EvenNumber {
                    num,
                })
            } else {
                Err(())
            }
        }
    }
}

// bring EvenNumber into scope
use some_mod::EvenNumber;

fn try_to_create_invalid_even_number() -> EvenNumber {
    EvenNumber {
        num: 1337, // compile error
    }
}

Throws:

error[E0451]: field `num` of struct `EvenNumber` is private
  --> src/lib.rs:24:9
   |
24 |         num: 1337,
   |         ^^^^^^^^^ private field

playground

When your tuple struct is defined in a different module (which can be simply a different file) or like the following:

mod mod_name {
    #[derive(Debug)]
    pub struct EvenNumber(i32);
    
    use std::convert::TryFrom;
    impl TryFrom<i32> for EvenNumber {
        type Error = ();

        fn try_from(value: i32) -> Result<Self, Self::Error> {
            if value % 2 == 0 {
                Ok(EvenNumber(value))
            } else {
                Err(())
            }
        }
    }
}

fn main() {
    // let num = mod_name::EvenNumber(5); // error[E0603]: tuple struct constructor `EvenNumber` is private
    
    use std::convert::TryFrom;
    let num = mod_name::EvenNumber::try_from(4);
    println!("num = {:?}", num.unwrap());
}

Then you wouldn't be able to construct that tuple directly with any number. You can test this by uncommenting the commented line in the above example. In Rust, the privacy of entities matter only towards the sibling or parental modules but not towards the same scope or children modules.

Related