What are traits and how do I use them in EK9?
← Classes and OOP · Ref: Q106
Traits define behaviour contracts with optional default implementations. Unlike abstract classes, traits cannot hold mutable state.
DEFINING A TRAIT
defines trait Printable display() <- rtn as String?
Methods can have bodies (defaults) or be abstract.
IMPLEMENTING
Use 'with trait of':
Product with trait of Printable, Describable override display() <- rtn as String: name
DEFAULT METHODS
Classes only need to override abstract methods or methods they want to customise.
TRAIT VS JAVA INTERFACE
EK9 traits support 'allow only' restriction and trait delegation beyond Java 8+ defaults.
See Q93 for class basics. See Q103 for abstract classes. See Q107 for multiple traits. See Q108 for implementing. See Q109 for composition. See Q110 for trait vs abstract. See Q264 for adapter pattern. See Q266 for delegation. See Q569 for pure methods. See Q576 for override resolution.
Example
defines module qa.oop.traits defines trait Printable display() <- rtn as String? Describable describe() <- rtn as String: "No description available" details() as abstract <- rtn as String? <?- Formattable extends Printable — creates a trait hierarchy. Classes using Formattable get Printable indirectly. -?> Formattable extends Printable format() as abstract <- rtn as String? defines class Product with trait of Printable, Describable name <- String() price <- 0.0 Product() -> name as String price as Float this.name: name this.price: price override display() <- rtn as String: name override details() <- rtn as String: `${name} costs ${price}` default operator ? <?- Report uses Formattable (which extends Printable). Printable is NOT an immediate trait of Report. -?> Report with trait of Formattable, Describable title <- String() Report() -> title as String this.title: title override display() <- rtn as String: title override details() <- rtn as String: `Report: ${title}` override format() <- rtn as String: `[${title}]` showFormatted() <- rtn as String: String() //Formattable is immediate — this is valid rtn: Formattable.format() default operator ? defines function <?- Functions cannot access trait methods directly. Must use the object reference instead. -?> formatItem() -> item as Printable <- result as String: item.display() defines program TraitsDemo() stdout <- Stdout() // === IMPLEMENTING A TRAIT === product <- Product("Widget", 9.99) stdout.println(`Display: ${product.display()}`) // === DEFAULT METHOD (from Describable) === stdout.println(`Describe: ${product.describe()}`) // === ABSTRACT METHOD (must override) === stdout.println(`Details: ${product.details()}`) // === TRAIT CHECK === stdout.println(`IsSet: ${product?}`) // === TRAIT HIERARCHY (Formattable extends Printable) === report <- Report("Q4 Sales") stdout.println(`Format: ${report.format()}`) stdout.println(`ShowFormatted: ${report.showFormatted()}`) // === FUNCTION WITH TRAIT PARAMETER === stdout.println(`FormatItem: ${formatItem(product)}`)
Common mistakes
E05120 — When implementing an abstract trait method in a class, the 'override' keyword is required. Product must use 'override display()' to implement Printable's abstract method. See ek9 -h E05120 for details.
Incorrect:
display()
Correct:
override display()
E50060 — EK9 has no toString() method. Use the $ prefix operator or string interpolation for string conversion. See ek9 -h E50060 for details.
Incorrect:
stdout.println(product.toString())
Correct:
stdout.println(`Display: ${product.display()}`)
E07075 — Traits are stateless behaviour contracts and cannot have properties or fields. State belongs in implementing classes. Use abstract accessor methods in the trait instead. See ek9 -h E07075 for details.
Incorrect:
name <- "default" display() <- rtn as String?
Correct:
display()
<- rtn as String?
E07070 — Traits cannot have constructors. They are stateless behaviour contracts with no instance creation logic. Construction belongs in implementing classes. See ek9 -h E07070 for details.
Incorrect:
Printable
Printable()
-> name as String
display()
<- rtn as String?
Correct:
Printable
display()
<- rtn as String?
E07270 — Trait methods cannot have access modifiers (private, protected). All trait methods are always public. This differs from Java interfaces where private methods are allowed since Java 9. See ek9 -h E07270 for details.
Incorrect:
private details() as abstract <- rtn as String?
Correct:
details() as abstract <- rtn as String?
E07810 — Dispatchers are only supported in classes, not traits. The 'as dispatcher' keyword requires a concrete class context for the runtime to route overloaded methods. See ek9 -h E07810 for details.
Incorrect:
format() as dispatcher -> item as Printable <- rtn as String?
Correct:
format() as abstract <- rtn as String?
E06160 — When a class declares 'with trait of Formattable' and Formattable extends Printable, only Formattable is an immediate trait. Calling Printable.display() is rejected because Printable is indirect. This prevents tight coupling to the trait hierarchy. See ek9 -h E06160 for details.
Incorrect:
rtn: Printable.display()
Correct:
rtn: Formattable.format()
E06170 — Trait method access (TraitName.method()) is only supported inside classes or dynamic classes that have that trait. Functions and programs cannot call trait methods directly. Use the object reference instead. See ek9 -h E06170 for details.
Incorrect:
<- result as String: Printable.display()
Correct:
<- result as String: item.display()
Other ways to ask this
- What is the EK9 equivalent of Java interfaces?
- How do traits differ from interfaces in EK9?
- How do I define behaviour contracts in EK9?
- Can traits have constructors or private methods in EK9?
Coming from another language?
Java: interfaces with defaults (8+). Python: ABCs/Protocol. Rust: traits with defaults. Go: implicit interfaces. Swift: protocols with extensions. EK9: 'with trait of', 'allow only' restriction, delegation with 'by'.
Keywords: define, swift, behaviour, trait, default, migrate, object-oriented, interface, method, abstract, contract, implement, protocol, printable