Why can't I extend a class in EK9?

← Classes and OOP · Ref: Q904

EK9 types are CLOSED by default — like Kotlin's classes which are final unless marked 'open'. You must explicitly opt into extensibility with 'as open'.

CLOSED BY DEFAULT

All classes, records, and built-in types cannot be extended unless marked:

  defines class
    Animal          closed — cannot extend
      name <- String()
    Dog extends Animal   ERROR E05030 — Animal is not open

OPT INTO EXTENSION

Use 'as open' to allow extension:

  defines class
    Animal as open       now extensible
      name <- String()
    Dog extends Animal   OK — Animal is open

BUILT-IN TYPES CANNOT BE EXTENDED

List, Dict, Optional, Result, and all built-in concrete types are CLOSED:

  MyList extends List of String    ERROR — List is closed

Use composition/delegation instead:

  defines class
    ValidatedList
      items as List of String: List() of String
      addValidated()
        -> item as String
        if length item > 0
          items += item

WHY CLOSED BY DEFAULT

- Like Kotlin's final by default — prevents fragile base class problem
- Forces explicit design for inheritance
- Encourages composition over inheritance (more flexible)
- Simplifies generated code (no virtual dispatch needed)
- Modern consensus: Java's open-by-default was a design mistake

RECORDS ARE ALWAYS CLOSED

Records cannot be marked 'as open' — they are pure value types:

  defines record
    Point
      x as Float: 0.0
      y as Float: 0.0

See Q93 for class basics. See Q97 for records vs classes.

Example

defines module qa.classes.closedtypes

  defines class

    <?-
      CLOSED class — cannot be extended.
      Like Kotlin's final (default behavior).
    -?>
    DatabaseConnection
      connectionUrl as String: String()

      DatabaseConnection()
        -> connectionUrl as String
        this.connectionUrl :=: connectionUrl

      getUrl() as pure
        <- rtn as String: connectionUrl

      default operator

    <?-
      OPEN class — explicitly allows extension.
      Like Kotlin's 'open class'.
    -?>
    Shape as abstract
      shapeName as String: String()

      Shape()
        -> shapeName as String
        this.shapeName :=: shapeName

      area() as pure abstract
        <- rtn as Float?

      getName() as pure
        <- rtn as String: shapeName

      default operator

    <?-
      Extends an open classthis is allowed.
    -?>
    Rectangle extends Shape
      rectWidth as Float: 0.0
      rectHeight as Float: 0.0

      Rectangle()
        ->
          rectWidth as Float
          rectHeight as Float
        super("Rectangle")
        this.rectWidth :=: rectWidth
        this.rectHeight :=: rectHeight

      override area() as pure
        <- rtn as Float: rectWidth * rectHeight

      default operator

    <?-
      Composition instead of inheritance.
      Used when the base type is closed.
    -?>
    ValidatedItems
      items as List of String: List() of String

      addItem()
        -> item as String
        if length item > 0
          items += item

      getCount() as pure
        <- rtn as Integer: length items

      default operator

  defines program

    ClosedTypesDemo()
      stdout <- Stdout()

      // Closed class — used directly, not extended
      conn <- DatabaseConnection("jdbc:postgresql://localhost:5432/mydb")
      stdout.println(`Connection: ${conn.getUrl()}`)

      // Open class — can be extended
      rect <- Rectangle(5.0, 3.0)
      stdout.println(`Shape: ${rect.getName()}, Area: ${rect.area()}`)

      // Composition — when you can't extend
      validItems <- ValidatedItems()
      validItems.addItem("hello")
      validItems.addItem("")
      validItems.addItem("world")
      stdout.println(`Valid items: ${validItems.getCount()}`)
Other ways to ask this
  • What does 'closed by default' mean in EK9?
  • How do I make a class extensible in EK9?
  • What is the 'as open' modifier in EK9?
  • Why does E05030 say 'not open to be extended'?

Coming from another language?

Kotlin: classes are final by default, use 'open' keyword — identical to EK9's approach. Java: classes are open by default — opposite of EK9, considered a historical design mistake. Rust: no inheritance at all — composition via traits only. Swift: classes are not final by default but 'final' keyword available. Go: no inheritance — composition only. C++: classes are extensible by default. EK9: closed by default like Kotlin — use 'as open' to allow extension.

Keywords: final, E05030, Kotlin, open, closed, inherit, composition, extend