How do I control field visibility in EK9?
← Classes and OOP · Ref: Q95
EK9 fields have fixed visibility rules that vary by construct type. There are no access modifiers on fields; the construct type determines visibility.
CLASSES: ALWAYS PRIVATE
Class properties are always private. Access them through explicit accessor methods:
defines class Person name <- String() name() as pure <- rtn as String: name
You write accessor methods to expose properties. There is no getter/setter annotation.
RECORDS: ALWAYS PUBLIC
Record properties are always public. Access them directly:
defines record Point x <- 0.0 y <- 0.0
Callers use 'point.x' directly. Records are data carriers by design.
COMPONENTS: ALWAYS PRIVATE
Component properties are always private, just like classes. Components are large-scale containers for dependency injection:
defines component Processor as abstract process() -> input as String <- rtn as String
Concrete components access their own fields internally.
TRAITS: NO FIELDS
Traits cannot have properties at all. They define method contracts with no retained state:
defines trait Printable show() as pure abstract <- rtn as String
NO ACCESS MODIFIERS ON FIELDS
Unlike methods (which can be public, protected, or private), fields have no access modifier in the grammar. Writing 'private name <- String()' or 'public x <- 0.0' causes a parse error. The construct type alone determines field visibility.
FIELDS MUST BE INITIALISED
All fields must be initialised at declaration, set in a constructor, or marked for injection with '!' on components. Uninitialised fields cause E08180.
See Q93 for class basics. See Q94 for constructors. See Q97 for records vs classes. See Q106 for traits. See Q111 for components. See Q573 for method access modifier rules.
Example
defines module qa.oop.visibility defines trait Printable show() as pure abstract <- rtn as String? defines class Person with trait of Printable name <- String() age <- Integer() Person() -> name as String age as Integer this.name: name this.age: age name() as pure <- rtn as String: name age() as pure <- rtn as Integer: age override show() as pure <- rtn as String: `Person: ${name}, age ${age}` default operator defines record Point x <- 0.0 y <- 0.0 Point() -> x as Float y as Float this.x: x this.y: y default operator defines component Processor as abstract process() abstract -> input as String <- rtn as String? default operator ? defines component UpperCaseProcessor extends Processor prefix <- "PROCESSED" override process() -> input as String <- rtn as String: `${prefix}: ${input.upperCase()}` default operator ? defines program VisibilityDemo() stdout <- Stdout() // === CLASS FIELDS: always private, use accessor methods === person <- Person("Alice", 30) stdout.println(`Name via accessor: ${person.name()}`) stdout.println(`Age via accessor: ${person.age()}`) stdout.println(person.show()) // === RECORD FIELDS: always public, access directly === point <- Point(3.0, 4.0) stdout.println(`Direct access x: ${point.x}, y: ${point.y}`) // === COMPONENT FIELDS: always private, used internally === processor <- UpperCaseProcessor() stdout.println(processor.process("hello world")) // === TRAIT: no fields, just method contracts === printable as Printable: person stdout.println(`Via trait: ${printable.show()}`) // === COMPARISON === point2 <- Point(3.0, 4.0) stdout.println(`Points equal: ${point == point2}`) person2 <- Person("Alice", 30) stdout.println(`Persons equal: ${person == person2}`)
Common mistakes
E06180 — Class fields are always private in EK9. There is no 'private' keyword because it is the only option. Access fields through accessor methods like 'name()'. See ek9 -h E06180 for details.
Incorrect:
person.name
Correct:
person.name()
E50060 — EK9 does not use Java-style 'getX()' accessors. The accessor method matches the field name: 'name()' not 'getName()'. See ek9 -h E50060 for details.
Incorrect:
person.getName()
Correct:
person.name()
E06180 — Component fields are always private. Even though the field 'prefix' exists on UpperCaseProcessor, it cannot be accessed from outside the component. Use the public methods instead. See ek9 -h E06180 for details.
Incorrect:
processor.prefix
Correct:
processor.process("hello world")
Other ways to ask this
- Are class properties public or private by default in EK9?
- How does encapsulation work in EK9?
- What is the difference between class, record, and component field visibility?
Coming from another language?
Java: fields private by convention with explicit getters/setters, no enforcement unless you add private. Python: no true private, underscore convention, properties via @property decorator. Rust: struct fields private by default outside module, pub for public. Go: uppercase for exported, lowercase for unexported. Kotlin: val/var properties with automatic getters/setters, backing field access. C#: fields private by default, properties with get/set accessors. EK9: class and component properties always private (no modifier allowed), record properties always public, traits have no fields. The construct type determines visibility with no override possible.
Keywords: accessor, property, trait, field, getter, object-oriented, encapsulation, class, record, E08180, component, visibility, public, private, setter