How does field encapsulation work in generic classes?

← Generics · Ref: Q682

Generic class fields follow the same access rules as non-generic classes: fields are always private. Public methods provide controlled access to internal state.

PRIVATE BY DEFAULT

Generic class fields are private. Accessing them from outside the class triggers E06180:

  box <- Box(42)
  box.content       // ERROR E06180: private field
  box.getContent()  // OK: public method

PUBLIC API METHODS

Provide accessor methods to expose state safely:

  getContent() as pure
    <- rtn as T: content

This allows reading without exposing the field directly.

ENCAPSULATION BENEFIT

Generic types can validate or transform values without exposing internals. The type parameter T flows through the public API but the storage is private.

DEFAULT OPERATORS

The 'default operator ?' and other default operators work with private fields because they are generated within the class scope.

See Q645 for public constructor requirement. See Q642 for constructor inference. See Q651 for constraint constructor match.

Example

defines module qa.genericsdeep.encapsulation

  defines class

    <?-
      Generic class with proper encapsulation.
      Fields are private, public methods provide access.
    -?>
    SafeBox of type T
      stored as T?

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

      SafeBox() as pure
        -> initialValue as T
        stored :=? initialValue

      //Public accessor: safe read access
      retrieve()
        <- rtn as T: T()
        if stored?
          rtn: T(stored)

      //Public check: is content available?
      hasValue() as pure
        <- rtn as Boolean: stored?

      //Public mutator: controlled write access
      store()
        -> newValue as T
        stored: newValue

      default operator ?

    <?-
      Generic wrapper adding validation logic.
      Internal state fully encapsulated.
    -?>
    ValidatedContainer of type T
      content as T?
      validationCount as Integer: Integer()

      ValidatedContainer() as pure
        content :=? T()

      ValidatedContainer() as pure
        -> initialContent as T
        content :=? initialContent

      //Public: access with side effect tracking
      access()
        <- rtn as T: T()
        if content?
          validationCount: validationCount + 1
          rtn: T(content)

      //Public: how many times has content been accessed?
      accessCount() as pure
        <- rtn as Integer: validationCount

      default operator ?

  defines program

    GenericEncapsulationDemo()
      stdout <- Stdout()

      box <- SafeBox(42)
      stdout.println(`Has value: ${box.hasValue()}`)

      retrieved <- box.retrieve()
      stdout.println(`Retrieved: ${retrieved}`)

      tracked <- ValidatedContainer("important")
      firstAccess <- tracked.access()
      stdout.println(`First: ${firstAccess}`)
      secondAccess <- tracked.access()
      stdout.println(`Second: ${secondAccess}`)
      stdout.println(`Accessed ${tracked.accessCount()} times`)

Common mistakes

E06180 — Fields in generic classes (and all EK9 classes) are always private. Accessing the 'stored' field directly from outside the class triggers E06180. Use public accessor methods like retrieve() instead. See ek9 -h E06180 for details.

Incorrect:

retrieved <- box.stored

Correct:

retrieved <- box.retrieve()
Other ways to ask this
  • What is E06180 private field not accessible?
  • Can I access generic class fields from outside?
  • How do I expose generic type state safely?

Coming from another language?

Java: generic fields can be protected or package-private. C++: template fields follow standard access control. Kotlin: generic properties follow standard access. Rust: struct fields can be pub. EK9: all fields private in classes (including generic classes), use methods for access.

Keywords: accessor, class, method, field, generic, type-parameter, E06180, API, private, encapsulation