Can a generic type constructor be private?

← Generics · Ref: Q645

EK9 generic type constructors are public, with ONE exception: the no-argument DEFAULT constructor may be declared 'default private'. This is the escape hatch for a generic that has an uninitialised conceptual-T property it cannot give an unset value to - privacy then stops external code creating a partially-constructed (null-field) instance, while the inferred constructor still supplies a real value.

THE PUBLIC RULE

The inferred (parameterised) constructor must be public so the compiler can infer type arguments and instantiate from any context:

  Container of type T
    item as T?
    Container() as pure
      item :=? T()                  // give the optional field an unset T - never null
    Container(val as T) as pure       // inferred constructor: must be public
      item :=? val

THE PRIVATE NO-ARG ESCAPE HATCH

When a generic cannot give its field an unset 'T()' (T may be abstract, may hold a function, or may lack a public no-arg constructor), declare the default private:

  default private Container() as pure

External code then cannot create an empty Container; it must pass a value to the inferred constructor.

STILL NOT ALLOWED

1. A private or protected PARAMETERISED constructor - type inference needs it public.
2. A protected no-arg default - it would still allow subclass uninitialised construction.

See Q642 for constructor inference. See Q643 for two-constructor requirement. See Q194 for generic class basics.
See Q682 for generic encapsulation. See Q683 for generic constructor rules.

Example

defines module qa.genericsdeep.publicconstructor

  defines class

    <?-
      Correct: both constructors are public (default access).
      Generic types require public constructors for inference.
    -?>
    Cache of type T
      stored as T?

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

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

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

      default operator ?

    <?-
      Correct: using 'default' keyword generates public constructor.
    -?>
    Slot of type T
      item as T?

      Slot() as pure
        item :=? T()

      Slot() as pure
        -> initialValue as T
        item :=? initialValue

      default operator ?

  defines program

    PublicConstructorDemo()
      stdout <- Stdout()

      // === PUBLIC CONSTRUCTORS ENABLE INFERENCE ===

      intCache <- Cache(42)
      stdout.println(`Cache set: ${intCache?}`)

      strSlot <- Slot("data")
      stdout.println(`Slot set: ${strSlot?}`)

      // === PUBLIC DEFAULT ENABLES EXPLICIT TYPING ===

      emptyCache <- Cache() of Float
      stdout.println(`Empty cache set: ${emptyCache?}`)

Common mistakes

E06060 — Generic type constructors must be public. A private constructor would prevent type inference and instantiation from external code. The compiler needs unrestricted access to constructors for generic type instantiation. See ek9 -h E06060 for details.

Incorrect:

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

Correct:

Cache() as pure
        -> initial as T
        stored :=? initial
Other ways to ask this
  • What is E06060 generic constructors must be public?
  • Can I use private constructors in a generic class?
  • When is a private constructor allowed on a generic type?

Coming from another language?

Java: generic classes can have private constructors (factory pattern). C++: templates can have private constructors. Rust: no constructor keyword, but 'new' can be private. Go: unexported functions serve as private constructors. EK9: generic constructors must be public for type inference and external instantiation.

Keywords: constructor, access, private, instantiation, constant, visibility, type-parameter, public, generic, E06060