Why does E07175 fire on my generic class with a 'T?' field and a default constructor?

← Generics · Ref: Q1346

A generic field declared 'item as T?' is uninitialised at declaration. If the only no-argument constructor is the compiler-generated 'default Container()', that constructor leaves 'item' NULL — and any later use (for example the synthetic '?' operator reading 'item?') would dereference null. This is the billion-dollar mistake, so EK9 rejects it at compile time with E07175.

  Error : E07175: 'Container' ...: default constructor must be 'private' when uninitialised properties exist

THE IDIOMATIC FIX: give the field an unset T()
Replace 'default Container()' with a real no-arg constructor that first-initialises the field to an unset T(). The field is then present-but-unset (never null):

  Container of type T
    item as T?
    Container() as pure
      item :=? T()                 // present-but-unset, never null
    Container() as pure
      -> initial as T
      item :=? initial

WHY ':=?' (not ':=' or ':')
In a pure constructor ':=' is forbidden (E08100). The guarded ':=?' assigns only if the field is unset, which is exactly 'first-initialise to an unset value'.

IF T() IS NOT AVAILABLE

When T may be abstract, may hold a function, or has no public no-arg constructor, you cannot call T(). Then declare 'default private Container()' instead - external code cannot create an empty one, and callers must use the inferred constructor with a value.

See Q1347 for creating empty instances. See Q1348 for the T() limits. See Q645 for the private no-arg escape hatch.

Example

defines module qa.genericsdeep.optionalfieldnullsafety

  defines class

    Container of type T
      item as T?

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

      Container() as pure
        -> initial as T
        item :=? initial

      hasItem() as pure
        <- rtn as Boolean: item?

      default operator ?

  defines program

    OptionalFieldDemo()
      stdout <- Stdout()

      emptyOne <- Container() of String
      stdout.println(`emptyOne hasItem: ${emptyOne.hasItem()}`)

      filled <- Container("hello")
      stdout.println(`filled hasItem: ${filled.hasItem()}`)

Common mistakes

E07175 — A generic 'default Container()' leaves a 'T?' field null. Give the field an unset T() in the no-arg constructor (item :=? T()), or declare the default private. See ek9 -h E07175 for details.

Incorrect:

default Container() as pure

Correct:

Container() as pure
        item :=? T()
Other ways to ask this
  • My generic 'default Container() as pure' is rejected — why?
  • How do I stop a generic's optional field being null?
  • E07175 default constructor must be private when uninitialised properties exist — on a generic

Coming from another language?

Java/Kotlin: a generic field T is null until assigned; reading it risks NullPointerException at runtime. Rust: Option<T> forces an explicit None. EK9: a 'T?' field must be made present-but-unset (item :=? T()) or the type must forbid empty construction (default private) - the null is eliminated at compile time, not deferred to runtime.

Keywords: default, uninitialised, T?, field, E07175, null, generic, optional, constructor, guarded-assignment, present-but-unset