What is the difference between record and class field visibility in EK9?

← Classes and OOP · Ref: Q900

EK9 records and classes have OPPOSITE default field visibility. This catches many developers coming from Java where both are the same.

RECORD FIELDS: PUBLIC by default
Like Kotlin data classes or Rust structs, record fields are directly accessible:

  defines record
    Point
      x as Float: 0.0
      y as Float: 0.0
  origin <- Point()
  xValue <- origin.x         OK — fields are public

CLASS FIELDS: PRIVATE by default
Like Java, class fields are encapsulated — access through methods only:

  defines class
    Person
      name <- String()
  person <- Person()
  person.name               ERROR — field is private
  person.getName()           OK — access through method

WHY THE DIFFERENCE

- Records are VALUE TYPES — transparent data carriers like database rows or JSON objects
- Classes are BEHAVIOR TYPES — encapsulated objects with invariants to protect
- Records can't have additional methods (only operators)
- Classes can have methods and protect internal state

RECORD RULES

- Records CAN have operators (default operator generates them)
- Records CANNOT have additional methods
- Records are closed-by-default like classes — mark a base record 'as open' to allow extension (see Q1297 for the full record-inheritance pattern)
- Record fields are PUBLIC — there's nothing to protect since records are transparent
- Records are mutable; mutability is gated by 'as pure' on functions and methods, not on the type (see Q97)

CLASS FEATURES

- Classes CAN have methods and operators
- Class fields are PRIVATE — encapsulation protects invariants
- Classes can use 'as open' to allow extension (same rule as records)
- Classes can implement traits

See Q93 for class basics. See Q97 for records vs classes overview. See Q1297 for record-to-record inheritance.

Example

defines module qa.classes.recordvsclass

  defines record

    <?-
      Record fields are PUBLIC — transparent data carrier.
      Like Kotlin data class or Rust struct with pub fields.
    -?>
    Measurement
      sensorId as String: String()
      reading as Float: 0.0
      timestamp as Integer: 0

      Measurement()
        ->
          sensorId as String
          reading as Float
          timestamp as Integer
        this.sensorId :=: sensorId
        this.reading :=: reading
        this.timestamp :=: timestamp

      default operator

  defines class

    <?-
      Class fields are PRIVATE — encapsulated behavior.
      Access through methods only, like Java.
    -?>
    SensorMonitor
      sensorName as String: String()
      latestReading as Float: 0.0
      readingCount as Integer: 0

      SensorMonitor()
        -> sensorName as String
        this.sensorName :=: sensorName

      recordReading()
        -> newReading as Float
        latestReading :=: newReading
        readingCount++

      getLatestReading() as pure
        <- rtn as Float: latestReading

      getReadingCount() as pure
        <- rtn as Integer: readingCount

      getSensorName() as pure
        <- rtn as String: sensorName

      default operator

  defines program

    RecordVsClassDemo()
      stdout <- Stdout()

      // RECORD: fields are PUBLIC — direct access
      measurement <- Measurement("sensor-1", 23.5, 1000)
      stdout.println(`Sensor: ${measurement.sensorId}`)
      readingVal <- measurement.reading
      timeVal <- measurement.timestamp
      stdout.println(`Reading: ${readingVal}`)
      stdout.println(`Time: ${timeVal}`)

      // CLASS: fields are PRIVATE — access through methods
      monitor <- SensorMonitor("temperature")
      monitor.recordReading(23.5)
      monitor.recordReading(24.1)
      stdout.println(`Monitor: ${monitor.getSensorName()}`)
      stdout.println(`Latest: ${monitor.getLatestReading()}`)
      stdout.println(`Count: ${monitor.getReadingCount()}`)

Common mistakes

E06180 — Class fields are PRIVATE by default in EK9 — access them through methods (getSensorName()), not by direct field reference. See ek9 -h E06180 for details.

Incorrect:

monitor.sensorName

Correct:

monitor.getSensorName()
Other ways to ask this
  • Are record fields public or private in EK9?
  • Why can I access record fields but not class fields?
  • How does visibility differ between records and classes in EK9?
  • What is the default visibility for EK9 records vs classes?

Coming from another language?

Java: both class and record fields are private by default. Kotlin: data class properties are public, class properties are public unless private. Rust: struct fields are private by default, pub required. Go: exported if capitalized (uppercase), unexported if lowercase — applies to both struct and interface fields. Python: all attributes are public, _prefix convention for private. EK9: records are PUBLIC (like Kotlin data class), classes are PRIVATE (like Java) — opposite defaults based on type purpose.

Keywords: access, encapsulation, private, visibility, record, field, public, class