Properties and events
Raven uses a property-first model. val and var define the public mutability
contract, while storage is an implementation detail.
Property kinds
val: publicly read-only after initialization.var: publicly mutable after initialization.- Accessors may be omitted when the declaration contract fully defines the intended surface.
val may declare set/init accessors. A set accessor on val must
be less accessible than the getter. init remains compatible with public
object-initializer assignment.
Storage (auto) properties
Storage properties are declarations without computed implementation:
val Name: string
var Count: int = 0
For storage properties, Raven can infer the property type from an initializer when the annotation is omitted:
class Foo {
val x = 2 // inferred as int
}
Without an initializer, a type annotation is required.
The compiler synthesizes backing storage. You can still provide accessors to refine behavior:
val Status: OrderStatus { private set; }
var Score: int {
get => field
set => field = max(0, value)
}
Accessor defaults:
| Contract | Getter | Setter |
|---|---|---|
val |
public | none |
var |
public | public |
When a storage property declares an accessor list that omits get, the compiler still synthesizes the getter to preserve the val/var contract. For example, val Status: OrderStatus { private set; } emits a public getter and a private setter.
For var, writable access is part of the public contract: explicit set/init accessors must match the property's accessibility.
Explicit accessor lists are required when the property surface differs from the
default val/var contract.
Computed properties
Computed properties provide implementation directly and do not use synthesized storage unless explicitly needed:
val FullName: string => first + " " + last
init accessor and initialization phase
init permits assignment only during initialization and preserves val
semantics:
val Name: string { init; }
Initialization includes inline initializers, constructors (init(...)),
initializer blocks, and object initializers.
field and indexers
Inside storage-property accessors, field references the synthesized backing
field.
Indexers are a property form using self[...] and follow the same val/var,
get/set/init, and accessibility rules:
var self[index: int]: string {
get => items[index]
set { items[index] = value }
}
Private storage property lowering
Private storage properties may be lowered to field-only emission when observable semantics are preserved.
private val count: int
private var score: int
This lowering affects emitted representation only. In semantic analysis and the symbol model, the member remains a property and continues to participate in property diagnostics/tooling.
Lowering is valid for private storage properties that do not require accessor logic. Computed properties are not lowered this way.
When applied, reads/writes may lower to direct field access and accessor methods may be omitted from emitted metadata. Source-level behavior remains property-centric.
Events
Events let a type notify any registered handlers when something happens, without knowing which objects are listening.
Events expose a delegate-like member that supports handler subscription via
+= and -=. An event declaration specifies the event keyword, a name, and
the event handler type:
class Button {
event Clicked: System.Action;
}
Event accessors
Custom events supply add and remove accessors, which receive the implicit
value parameter of the handler type:
class Button {
event Clicked: System.Action {
add { /* register value */ }
remove { /* unregister value */ }
}
}
Auto-implemented events
When an event declaration ends with ;, the compiler synthesizes a hidden
backing field and trivial add/remove accessors. Auto-events are the only
events that can be invoked directly, and invocation is only permitted inside
the declaring type:
class Button {
event Clicked: System.Action? // Events can be null
func Raise() -> unit {
Clicked?();
}
}