Records, enums and components
Records
A record is Unnameable's struct equivalent — a named collection of fields with no methods.
Fields are immutable by default. To make all fields mutable mark the record with mut:
Fields can carry default values. Without defaults the compiler zero-initializes.
Volatile records
volatile tells the compiler not to optimize away reads and writes to the record's fields — useful for memory-mapped hardware registers or shared memory:
This behaves the same as volatile in C.
Instantiation
There are exactly two ways to instantiate a record:
The first form initializes fields explicitly. The second default-initializes — zeros unless the record declared default values.
Passing records to functions
Records can be passed to and returned from functions. Fields are accessed with dot notation:
record Bounds {
i32 width
i32 height
}
record Rect {
i32 x
i32 y
}
func dimensions(Rect r):Bounds {
trace r.x
return Bounds{width=r.x, height=r.y}
}
Limitations
- No methods
- No nesting — a record cannot contain another record directly
- No
heapon individual fields — storage is tied to the instance
Export
Records can be marked export to expose them to external linkage and the stub system:
Enums
Enums in Unnameable are scoped — variants cannot leak into the surrounding namespace. They only work on integer types.
The default underlying type is i32. To specify a different integer type declare it after the enum name:
Accessing variants
Variants are accessed using :: notation:
Using enums in functions
func get_http_code(HttpStatus status):i32 {
if(status == HttpStatus::OK) {
return 200
} elif(status == HttpStatus::NotFound) {
return 404
} elif(status == HttpStatus::InternalServerError) {
return 500
}
return 0
}
func main:i32 {
auto code = HttpStatus::OK
i32 raw_code = get_http_code(code)
trace raw_code
return 0
}
Export
Enums can be marked export to expose them to the stub system:
Components
A component is a record with additional capabilities — methods, field injection, an optional init constructor, and access to self.
component Player {
i32 speed
i32 strength
func get_player_speed():i32 {
return self.speed
}
func buff_player:void {
self.speed = self.speed + 10
self.strength = self.strength + 10
}
init(i32 player_speed, i32 player_strength) {
self.speed = player_speed
self.strength = player_strength
}
}
Self
Inside a method, fields and other methods are accessible via self:
self. is optional — the compiler will resolve bare field names inside a method automatically. Both forms are valid:
The init constructor
init is a special function that lets you construct a component with custom starting values. It always returns void and is not mandatory — if you don't define one, the new initialization syntax is unavailable.
init(i32 player_speed, i32 player_strength) {
self.speed = player_speed
self.strength = player_strength
}
Instantiation
If the component has an init:
Despite the new keyword this is stack allocated. For heap allocation:
Default initialization (zeros or declared defaults):
Methods
Methods follow the same syntax as global functions. They live inside the component body and have access to self:
Field injection
Injection pulls all fields from a record directly into the component at the frontend — no extra indirection involved:
record Buffs {
i32 super_speed = 10
i32 super_strength = 10
}
component Player {
i32 speed
i32 strength
inject record Buffs
func buff_player:void {
self.speed = self.speed + self.super_speed
self.strength = self.strength + self.super_strength
}
}
After injection super_speed and super_strength become direct fields of Player as if they were declared there.
To inject a single field only use @:
Warning: The compiler treats field name collisions as a hard error regardless of which records the fields came from. If two injected records share a field name the compiler will reject it. To avoid this embed the record as a field instead —
Buffs buffs— and access it via dot notation.
Only records can be injected
You cannot inject from another component — only records are valid injection sources.
Export
Marking a component export exposes it and all its methods to external linkage and the stub system:
Individual methods can also be targeted: