Define a Percentage type constrained to values between 0 and 100.
← Advanced Type System · Ref: Q1185
Use 'constrain as' with range checks:
defines type Percentage as Integer constrain as >= 0 and <= 100
A bare constructor ASSERTS validity: Percentage(150) Panics at runtime on an out-of-range value, and any value the compiler can PROVE breaches the constraint is the compile error E08260 — moving it into a variable or a constant first does not defer the check. For untrusted input use the fallible factory Percentage().of(value), which returns an UNSET object on violation (never Panics) — check with '?' after construction. Constrained types are disconnected from their base type — Percentage is NOT an Integer subtype.
See Q257 for constrained types. See Q1074 for constrained in record. See Q720 for constrainable types.
Example
defines module qa.advancedtypes.constrainedtype defines type Percentage as Integer constrain as >= 0 and <= 100 defines record ExamScore studentName as String: String() score as Percentage: Percentage(0) ExamScore() -> studentName as String score as Percentage this.studentName :=: studentName this.score :=: score default operator defines program ConstrainedTypeDemo() stdout <- Stdout() //Valid — within range passing <- Percentage(85) stdout.println(`Valid score set: ${passing?}`) if passing? stdout.println(`Score: ${passing}`) //Invalid — above 100: the fallible factory returns unset (a bare Percentage(150) would Panic at //runtime; so would Percentage(limit) after 'limit <- 150', and BOTH are the compile error //E08260 because the value is statically known either way) tooHigh <- Percentage().of(150) stdout.println(`Too high set: ${tooHigh?}`) //Invalid — below 0: of() returns unset negative <- Percentage().of(-5) stdout.println(`Negative set: ${negative?}`) //Boundary values zero <- Percentage(0) hundred <- Percentage(100) stdout.println(`Zero set: ${zero?}`) stdout.println(`Hundred set: ${hundred?}`) //Use in a record result <- ExamScore("Alice", Percentage(92)) stdout.println(`${result}`)
Common mistakes
E08260 — A bare constructor ASSERTS validity, so a value the compiler can prove is out of range is rejected now rather than Panicking later. Assigning the literal to a variable or a module constant first does NOT defer the check — flow analysis follows the value that ARRIVES at the constructor, through reassignment, branches, ternary and switch arms, guards and captures. Use the fallible factory .of() when the value genuinely is not known until runtime; it returns an UNSET object instead of Panicking. See ek9 -h E08260 for details.
Incorrect:
passing <- Percentage(185)
Correct:
passing <- Percentage(85)
E50060 — Constrained types are disconnected from their base type, so passing the Integer 92 where a Percentage is expected fails to resolve the ExamScore constructor — wrap it as Percentage(92). See ek9 -h E50060 for details.
Incorrect:
ExamScore("Alice", 92)
Correct:
ExamScore("Alice", Percentage(92))
Other ways to ask this
- Write code to create a constrained numeric type that rejects out-of-range values
- I need a type that only accepts integers from 0 to 100 for percentage values
- In Rust I'd use a newtype with validation. Write the EK9 constrained type equivalent
- Build a Percentage constrained type and show how invalid values become unset
Coming from another language?
Java: no built-in, uses Bean Validation @Min/@Max. Python: no type-level constraints. Rust: newtype with manual validation. Kotlin: value class with init check. EK9: 'constrain as' creates a validated, disconnected type.
Keywords: disconnected, percentage, constrain as, validation, constrained, type, range