How do sealed classes differ from sealed traits in EK9?
← Type Hierarchy Constraints · Ref: Q608
Both classes and traits support 'allow only' to create sealed hierarchies, but they differ in requirements and use cases.
SEALED CLASSES
Sealed classes require 'as open' (or 'as abstract') because classes are closed by default. Sealed classes use single inheritance: each permitted subclass extends exactly one sealed parent. This creates a clean, linear hierarchy well-suited for data types.
SEALED TRAITS
Traits are inherently open (they must be implementable), so they don't need 'as open'. Sealed traits allow multiple implementation: a class can implement multiple sealed traits. This creates richer type relationships suited for behavioral contracts.
E05240 AND E05270
Both sealed classes and traits enforce E05240 (unpermitted concrete type tries to implement/extend). Sealed classes additionally enforce E05270 if they forget 'as open'. Traits never get E05270 because traits are always implementable.
WHEN TO USE EACH
Use sealed classes when you want a fixed set of data variants (like Result, Optional). Use sealed traits when you want a fixed set of behavioral capabilities that classes can mix.
DISPATCHER EXHAUSTIVENESS
Both sealed classes and sealed traits trigger exhaustive dispatch checking (E05260). A dispatcher over either type must handle all permitted types.
See Q298 for sealed traits. See Q301 for sealed classes. See Q607 for E05270. See Q302 for dispatchers with sealed classes.
Example
defines module qa.typehierarchy.sealedcomparison defines trait //Sealed trait: no 'as open' needed (traits are inherently open) Printable allow only Document, Spreadsheet, Slide render() as abstract <- rtn as String? defines class //Sealed class: requires 'as open' explicitly Vehicle allow only Car, Truck as open describe() <- rtn as String: "vehicle" Car extends Vehicle override describe() <- rtn as String: "car" Truck extends Vehicle override describe() <- rtn as String: "truck" //Classes implementing the sealed trait Document with trait of Printable override render() <- rtn as String: "document" Spreadsheet with trait of Printable override render() <- rtn as String: "spreadsheet" Slide with trait of Printable override render() <- rtn as String: "slide" defines function testSealedClass() car <- Car() truck <- Truck() require car.describe() == "car" require truck.describe() == "truck" testSealedTrait() doc <- Document() sheet <- Spreadsheet() slide <- Slide() require doc.render() == "document" require sheet.render() == "spreadsheet" require slide.render() == "slide"
Common mistakes
E05270 — Sealed classes require as open because classes are closed by default. Without it, the allow only list is contradictory since no class can extend a closed type. See ek9 -h E05270 for details.
Incorrect:
Vehicle allow only Car, Truck
Correct:
Vehicle allow only Car, Truck as open
E05120 — When overriding the describe method from the sealed parent Vehicle, the override keyword is mandatory. Omitting it triggers shadowing detection. See ek9 -h E05120 for details.
Incorrect:
describe()
Correct:
override describe()
Other ways to ask this
- Should I use a sealed class or a sealed trait?
- What is the difference between allow only on classes and traits?
- When to seal a class vs a trait in EK9?
Coming from another language?
Java: sealed interfaces and sealed classes have similar distinction (Java 17+). Kotlin: sealed classes and sealed interfaces with permits. Scala: sealed traits and sealed abstract classes. Rust: enums (similar to sealed classes), no sealed traits. EK9: sealed classes require 'as open', sealed traits inherently open, both support exhaustive dispatch.
Keywords: trait, exhaustive, closed, only, allow, single, E05240, E05270, circular, difference, sealed, inherit, type, class, hierarchy, multiple