Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Force use of a constructor [duplicate]

Tags:

rust

I want to give some business-rule guarantees about certain structs. For example, that an EmailAddress is a valid email, or that a DateRange has a from that lies before a from, and so on. So that when passing such a value around, it is guaranteed to adhere to all business rules for that struct.

struct InvalidEmailAddress;
struct EmailAddress {
  value: String
}

impl EmailAddress {
  fn new(value: String) -> Result<Self, InvalidEmailAddress> {
    if value.contains("@") { // I know, this isn't any sort of validation. It's an example.
        Ok(Self { value })
    } else {
        Err(InvalidEmailAddress)
    }
  }
}

Ignoring that now new() behaves unexpected (it probably would be better to use a build() method), this brings an issue: When someone builds an EmailAddress through the constructor, it is guaranteed to be "valid". But when someone constructs it as normal struct, it may not be.:

let guaranteed_valid = EmailAddress::new(String::from("[email protected]")).unwrap();
let will_crash = EmailAddress::new(String::from("localhost")).unwrap()
let unknown_valid = EmailAddress { value: String::from("hi-at-example.com") }

I would like to prohibit any users of those structs from constructing them directly like in the last line.

Is that possible at all? Are there any more ways someone could construct an EmailAddress in an invalid way?

I'm OK with placing the structs in a module, and using public/private visibility if that is possible at all. But from what I can see, any code that wants to now enforce the EmailAddress type, say a send_report(to: EmailAddress) would have access to the struct and can build it directly. Or am I missing something crucial?

like image 660
berkes Avatar asked Oct 17 '25 21:10

berkes


1 Answers

You need to place your struct in a module. That way any code outside of that module will only be able to access the public functionality. Since value is not public, direct construction will not be allowed:

mod email {
    #[derive(Debug)]
    pub struct InvalidEmailAddress;
    pub struct EmailAddress {
        value: String,
    }

    impl EmailAddress {
        pub fn new(value: String) -> Result<Self, InvalidEmailAddress> {
            if value.contains("@") {
                // I know, this isn't any sort of validation. It's an example.
                Ok(Self { value })
            } else {
                Err(InvalidEmailAddress)
            }
        }
    }
}

use email::EmailAddress;

fn main() {
    let e = EmailAddress::new("foo@bar".to_string()).unwrap(); // no error
    //let e = EmailAddress { value: "invalid".to_string() };   // "field `value` of struct `EmailAddress` is private"
}

Playground

More details on visibility in the Book.

like image 180
user4815162342 Avatar answered Oct 20 '25 16:10

user4815162342



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!