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