> I would still usually prefer email as just a string and validation as a separate property, and they both belong to some other object. Unless you really only want to know if XYZ email exists, it's usually something more like "has it been validated that ABC user can receive email at XYZ address".
> Is the user account validated? Send an email to their email string. Is it not validated? Then why are we even at a point in the code where we're considering emailing the user, except to validate the email.
You are looking at this single type in isolation. The benefit of an email type over using a string to hold the email is not validating the actual string as an email address, it's forcing the compiler to issue an error if you ever pass a string to a function expecting an email.
Consider function `foo`, which takes an email and a username parameter.
This compiles just fine but is a logic error:
void foo (char *email, char *username);
...
char *my_email = parse_input ();
char *my_user = parse_input ();
foo (my_user, my_email);
Using a separate type for email means that this refuses to compile: void foo (email_t *email, char *username);
...
email_t *my_email = parse_input ();
char *my_user = parse_input ();
foo (my_user, my_email); // Compiler error
I hope you can see the value in having the compiler enforce correctness. I have a blog post on this, with this exact example.