When must a generic class use 'default private' for its no-argument constructor?
← Generics · Ref: Q1347
The idiomatic way to keep a generic's 'T?' field non-null is to give it an unset T() in the no-arg constructor ('item :=? T()'). But T() is not always available - it requires the concrete type used to parameterise the generic to have a public no-argument constructor. T() is NOT available when:
1. T may be ABSTRACT - an abstract type cannot be constructed (E10030).
2. T may hold a FUNCTION - a function cannot be constructed with T() (E06110).
3. T may have NO public no-arg constructor - for example a type whose own default is 'default private' (E10031).
4. T's no-arg constructor may be NON-pure while the generic calls 'T()' in a PURE constructor - a pure 'T()' demands a pure no-arg constructor on the parameterising type (E10032).
These constructor requirements (existence, public access, purity) are properties of the CONCRETE type, so they are only checked at parameterisation - not when the generic is defined.
In these cases, declare the no-arg default PRIVATE:
Holder of type T item as T? default private Holder() as pure // escape hatch - no T() needed Holder() as pure -> initial as T item :=? initial
WHAT 'default private' GIVES YOU
External code CANNOT create an empty Holder (the no-arg constructor is private), so it can never observe a null field. Callers must use the inferred constructor with a real value. The inferred (parameterised) constructor stays public so type inference still works.
DECISION RULE
- T always has a public no-arg constructor (String, Integer, your own value types): use 'item :=? T()' so empty construction is allowed and the field is present-but-unset.
- T might not (abstract bound, function, or no-arg-private types): use 'default private' and construct only with a value.
See Q1346 for the E07175 error. See Q1348 for the two safe shapes. See Q645 for the constructor access rule.
Example
defines module qa.genericsdeep.defaultprivateescapehatch defines class Holder of type T item as T? default private Holder() as pure Holder() as pure -> initial as T item :=? initial get() as pure <- rtn as T: item default operator ? defines program EscapeHatchDemo() stdout <- Stdout() //Cannot do 'Holder() of String' here - the no-arg default is private. filled <- Holder("present") stdout.println(`filled set: ${filled?}`)
Common mistakes
E07175 — A generic with an uninitialised 'T?' field must make its no-arg default constructor 'private' (E07175); a public 'default' no-arg constructor would let external code create an empty instance whose field is never set. Keep it 'default private' so callers must supply a value via the inferred constructor. See ek9 -h E07175 for details.
Incorrect:
default Holder() as pure
Correct:
default private Holder() as pure
Other ways to ask this
- I cannot call T() in my generic - now what?
- My generic holds an abstract type or a function and item :=? T() fails
- How do I write a generic with an optional field that cannot be defaulted?
Coming from another language?
Java: a generic with an abstract or functional T simply leaves the field null and you hope no one reads it. EK9: you must choose - either guarantee an unset value with T(), or forbid empty construction with 'default private'. Either way the null is designed out at compile time.
Keywords: E10031, E10030, function, constructor, abstract, E07175, T, no-arg, pure, generic, private, escape-hatch, E06110, default, E10032