A function returns a type that cannot be invalid by construction. Validation throws knowledge away; parsing keeps it in the type.
The everyday equivalent is connectors. A power plug does not fit a USB port not because someone inspects it at the port, but because the shape is different. Validation is a “do not insert here” sticker; parsing is a differently shaped connector. The first kind of error is caught at runtime, and only if nobody forgot the check; the second is never made at all. The same analogy carries over to the neighbouring cards: 3 — there are exactly as many connectors as there are meaningful variants, and an extra one cannot even be manufactured; LSP (1) — if the plug fits, the pins must carry the voltage promised on the label.
// record + compact constructor = smart constructor
public record Email(String value) {
public Email {
if (!value.matches(".+@.+\\..+")) throw new IllegalArgumentException(value);
}
public static Optional<Email> parse(String raw) {
try { return Optional.of(new Email(raw)); }
catch (IllegalArgumentException e) { return Optional.empty(); }
}
}
// downstream, Email is always valid — no repeated checks// newtype with a private field — the canonical form
pub struct Email(String); // private field, can't be built from outside
impl Email {
pub fn parse(raw: &str) -> Result<Self, EmailError> {
raw.contains('@').then(|| Email(raw.to_owned())).ok_or(EmailError::Invalid)
}
pub fn as_str(&self) -> &str { &self.0 }
}// private field + constructor — the only way in
type Email struct{ value string } // zero value is useless — on purpose
func ParseEmail(raw string) (Email, error) {
if !strings.Contains(raw, "@") {
return Email{}, fmt.Errorf("invalid email: %q", raw)
}
return Email{value: raw}, nil
}
func (e Email) String() string { return e.value }# frozen dataclass + factory; NewType for cheap cases
from dataclasses import dataclass
@dataclass(frozen=True, slots=True)
class Email:
value: str
def __post_init__(self):
if "@" not in self.value:
raise ValueError(f"invalid email: {self.value!r}")
# or pydantic: class Email(BaseModel): value: EmailStr — same thing, on steroids