Smart Constructor
A function that is the only way to create a value of a given type and that checks the type's rules, so every value that exists is guaranteed to be valid.
Also known as: Smart Constructors, Validating Constructor, Factory Function with Validation
Category: Software Development
Tags: software-design, software-development, programming, functional-programming, type-systems, domain-modeling, validation
Explanation
A regular constructor accepts whatever you give it. `new EmailAddress("not an email")` happily creates an invalid email address, and every function that receives it now has to wonder whether it's real.
A smart constructor fixes that. You make the raw constructor private, and expose one function that validates the input and either returns a valid value or reports a failure. Since there's no other way in, any `EmailAddress` anywhere in the system went through that check.
**How it works**:
1. **Hide the real constructor** (a private constructor, an unexported type, a module boundary)
2. **Expose a creation function** like `EmailAddress.create(raw: string)` or `parseEmail(raw)`
3. **Validate inside it**: format, range, length, whatever the concept requires
4. **Return a result that can fail**: an `Option`, a `Result` / `Either`, or throw at the boundary if that's your language's convention
5. **Trust the type everywhere else**: functions that accept an `EmailAddress` never check it again
**Examples**:
- `PositiveInteger.create(n)` returns `None` for zero and negative numbers
- `Percentage.create(x)` only accepts values between 0 and 100
- `NonEmptyList.fromList(items)` fails on an empty list
- `DateRange.create(start, end)` rejects ranges where the end comes before the start
**Where the name comes from**:
The term comes from the Haskell community, where a module exports a type without its data constructor and provides a validating function instead. The same idea shows up everywhere today: factory methods in object-oriented code, `TryFrom` implementations in Rust, and the `create` functions Scott Wlaschin uses throughout Domain Modeling Made Functional.
**Why it matters**:
- **Validation happens exactly once**, in one place, instead of being scattered across the codebase
- **Invalid values can't exist**, so downstream code gets simpler (see Make Illegal States Unrepresentable)
- **The rules live with the type**, where the next developer will find them
- **Failure is explicit**: the return type tells callers that creation can fail, so they handle it
**How it connects**:
Smart constructors are where Parse, Don't Validate actually happens: raw input goes in, a precise type comes out. They pair naturally with the newtype pattern and value objects, and they're the standard fix for Primitive Obsession. The book Secure by Design builds its "domain primitives" on exactly this idea.
Keep in mind that a smart constructor only protects what it checks. If a rule matters, it belongs inside the constructor, not in a comment next to it.
Related Concepts
← Back to all concepts