How do I use constrained types as function parameters for type safety?
← Advanced Type System · Ref: Q721
Constrained types create type-safe function boundaries that eliminate an entire category of invalid-argument bugs at compile time.
DISCONNECTED TYPES ENFORCE API CONTRACTS
When a function takes a constrained type, you cannot pass the raw base type:
printName()
-> name as Name
//...
printName("Alice") //compile error: String is not Name
printName(Name("Alice")) //correct: construct the constrained type
CONSTRUCTION IS THE VALIDATION BOUNDARY
The caller must construct the constrained type, which validates untrusted input via the fallible factory:
if validName <- Name().of(userInput) printName(validName) //guaranteed valid
If the input violates the constraint, of() yields an unset value and the guard's else branch handles it. A bare constructor instead ASSERTS validity — it Panics on a set out-of-range value (and a violating literal is the compile error E08260) — so use of() for untrusted input.
GUARD AT THE BOUNDARY
Use the guard pattern to validate at system entry points:
if age <- DrivingAge().of(rawInput) processDriver(age) //DrivingAge is validated else stdout.println("Invalid age")
Inside processDriver, the DrivingAge is guaranteed to be in range.
CONSTRAINED TYPES IN CLASS PROPERTIES
Classes with constrained type fields enforce validity at construction:
Book
title as BookTitle?
author as AuthorName?
Every Book instance has a validated title and author.
See Q257 for constrained type overview. See Q269 for input validation patterns. See Q722 for type hierarchy.
Example
defines module qa.advancedtypes.constrainedparameters defines type Name as String constrain as matches /^[a-zA-Z -]+$/ DrivingAge as Integer constrain as >= 16 and <= 100 BookTitle as String constrain as matches /^[a-zA-Z0-9 :'-]+$/ defines function <?- Takes a validated Name — cannot accept raw String. -?> formatGreeting() as pure -> name as Name <- greeting as String: `Hello, ${name}!` <?- Takes a validated DrivingAge — cannot accept raw Integer. -?> canRentCar() as pure -> age as DrivingAge <- allowed as Boolean? rentalMinimum <- 21 allowed: age >= rentalMinimum defines program ConstrainedParametersDemo() stdout <- Stdout() // === MUST CONSTRUCT CONSTRAINED TYPE FIRST === validName <- Name("Alice Smith") greeting <- formatGreeting(validName) stdout.println(greeting) // === GUARD PATTERN AT BOUNDARY (fallible factory for untrusted input) === userInput <- "Bob Jones" if checkedName <- Name().of(userInput) result <- formatGreeting(checkedName) stdout.println(result) else stdout.println("Invalid name provided") // === CONSTRAINED TYPE VALIDATES AT THE BOUNDARY === if driverAge <- DrivingAge().of(25) canRent <- canRentCar(driverAge) stdout.println(`Age 25, can rent: ${canRent}`) // === INVALID INPUT IS REJECTED: of() RETURNS UNSET (a bare DrivingAge(10) would Panic) === invalidAge <- DrivingAge().of(10) stdout.println(`Age 10 valid: ${invalidAge?}`) // === CONSTRAINED FIELDS IN RECORDS === title <- BookTitle("The Great Adventure") stdout.println(`Book title: ${title}`) // === MULTIPLE CONSTRAINED PARAMETERS === validTitle <- BookTitle("EK9 Guide") stdout.println(`Title valid: ${validTitle?}`)
Common mistakes
E50060 — String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details.
Incorrect:
greeting <- formatGreeting(validName).toUpperCase()
Correct:
greeting <- formatGreeting(validName)
E50060 — String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details.
Incorrect:
result <- formatGreeting(checkedName).toUpperCase()
Correct:
result <- formatGreeting(checkedName)
Other ways to ask this
- Why should I use constrained types in function signatures?
- Can I pass a String where a Name is expected?
- How do constrained types replace runtime validation?
Coming from another language?
Java: parameter validation via Bean Validation annotations (@Valid, @NotNull), checked at runtime. Python: type hints are advisory, validation with pydantic at runtime. Rust: newtype pattern enforces boundaries but requires manual From/Into implementations. Go: no type constraints, runtime validation in function body. Kotlin: value classes provide some type safety but still subtypes. EK9: constrained types as parameters eliminate invalid-argument bugs at compile time — disconnected types prevent passing raw values, construction validates constraints, guard pattern handles boundary validation.
Keywords: type-safe, function, boundary, construction, validate, constrained, parameter, api, guard