I have a couple of types that are mapping to database columns for a user record:
struct User {
id: UserId,
email: Email
}
struct UserId(u64);
struct Email(Option<String>);
This seems to be working fine because I can implement From<Option> for Email
impl From<Option<String>> for Email {
fn from(opt: Option<String>) -> Self {
if let Some(s) = opt {
if is_valid_email(&s) {
return Self(Some(s));
}
}
Self(None)
}
}
But I don't know if this is really what I want because ideally the user record itself would have an optional (valid) email. Not Email having an optional (valid) String.
struct User {
id: UserId,
email: Option<Email>
}
I tried this but I couldn't implement From<Option> for Option. This is required for the null values coming from the database. So I need to get a user record with email: None.
// This obviously doesn't work
impl From<Option<String>> for Option<Email> {
// ...
}
How do people normally handle this kind of thing? Is email: Email(Option<String>)
idiomatic?
Using a newtype like Email
to wrap an Option<T>
isn't idiomatic. You should do instead:
struct Email(String);
struct User {
id: UserId,
email: Option<Email>
}
As for the parsing, don't use From
. From
is for infallible conversions; you are better of with TryFrom
or, even better, with FromStr
.
FromStr
is similar to TryFrom<String>
in the sense that both are meant for fallible conversions, but with FromStr
you get the .parse
method for free, and if you are using serde_with, it has a handy DeserializeFromStr
macro for deriving deserialization which uses the underlying FromStr
implementation in your type:
use serde_with::DeserializeFromStr;
#[derive(DeserializeFromStr)]
struct Email(String);
4 Likes
Option<Email>
is the way to go, it's just that AFAIK SQLx makes that awkward to work with.
Assuming you're still using SQLx, you could try implementing sqlx::Type
and using the override described at Compile-time verification. This means you lose some of the compile-time guarantees the library gives you, and you can't use SELECT *
and the like, both of which are very annoying, but it's the best workaround I'm aware of.
If you're wondering about my reply to your other thread, I was looking at it for extra context since I remembered seeing it before and accidentally replied to the wrong one. 
Thank you for the reply! Yes I am still using sqlx and banging my head against this problem. I'm realizing that it really is just the sqlx query_as! macros that are causing the headache. I'll explore using sqlx::Type and maybe FromRow to see if I can get it to work the way I want.
Thank you!