Why must a generic class have both a default and an inferred-type constructor?

← Generics · Ref: Q643

EK9 requires generic types to have exactly two constructors: a default (no-argument) constructor and an inferred-type constructor whose parameters match the type parameters.

TWO CONSTRUCTOR RULE

Generic types need:
1. A default constructor: 'default MyType()' for creating unset instances
2. An inferred constructor: 'MyType(val as T)' for type-inferred creation

WHY BOTH ARE NEEDED

The default constructor enables:

  container <- Container() of String

Creates an instance when no initial value is available.

The inferred constructor enables:

  container <- Container("hello")

Type parameter inferred from the argument.

DEFAULT KEYWORD

The 'default' keyword generates a no-argument constructor:

  default Container() as pure

This creates a Container where T is unset.

See Q642 for constructor inference. See Q644 for constructor argument type matching. See Q194 for generic class basics.

Example

defines module qa.genericsdeep.twoconstructors

  defines class

    <?-
      Correct: generic class with both default and inferred constructors.
      Default constructor creates unset instance.
      Inferred constructor enables type inference.
    -?>
    Holder of type T
      stored as T?

      Holder() as pure
        stored :=? T()

      Holder() as pure
        -> initial as T
        stored :=? initial

      retrieve()
        <- rtn as T: T()
        rtn :=? T(stored)

      default operator ?

    <?-
      Another correct example with multi-parameter generic.
      Both default and inferred constructors present.
    -?>
    KeyVal of type (K, V)
      theKey as K?
      theValue as V?

      KeyVal() as pure
        theKey :=? K()
        theValue :=? V()

      KeyVal() as pure
        ->
          k as K
          v as V
        theKey :=? k
        theValue :=? v

      default operator ?

  defines program

    TwoConstructorDemo()
      stdout <- Stdout()

      // === DEFAULT CONSTRUCTION (needs explicit type) ===

      emptyHolder <- Holder() of Integer
      stdout.println(`Empty holder set: ${emptyHolder?}`)

      // === INFERRED CONSTRUCTION ===

      filledHolder <- Holder("data")
      stdout.println(`Filled holder set: ${filledHolder?}`)

      // === MULTI-PARAM DEFAULT ===

      emptyKV <- KeyVal() of (String, Float)
      stdout.println(`Empty KV set: ${emptyKV?}`)

      // === MULTI-PARAM INFERRED ===

      filledKV <- KeyVal("price", 9.99)
      stdout.println(`Filled KV set: ${filledKV?}`)

Common mistakes

E06040 — A generic type requires both a default (no-argument) constructor and an inferred-type constructor; deleting the no-arg constructor leaves only the inferred one and fails. See ek9 -h E06040 for details.

Incorrect:

      Holder() as pure
        -> initial as T

Correct:

      Holder() as pure
        stored :=? T()

      Holder() as pure
        -> initial as T
Other ways to ask this
  • What is E06040 generic type requires two constructors?
  • Why do I need two constructors for a generic class in EK9?
  • What constructors does a generic type need in EK9?

Coming from another language?

Java: no constructor requirements for generics, relies on 'new T()' (impossible due to erasure). C++: default-constructible via 'requires std::default_initializable<T>'. Rust: Default trait optional. Go: zero-value initialization built in. Kotlin: no mandatory constructor count. EK9: two constructors required, default + inferred, enabling both patterns.

Keywords: default, instantiation, requirement, type-parameter, generic, two, inferred, class, constructor, constant, E06040