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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With