Reference
The effect set
What a function can be doing, tracked whether or not a subsystem is available.
An effect is what a function can be doing. Effects are inferred from the calls a function makes rather than declared on it, and they are tracked whether or not the subsystem behind them exists in this target.
Where an effect comes from
A call into a capability. The registry declares the effects of every host-visible operation, which is what makes inference possible at all: a function is pure because of what it does not call, and the compiler can see that without being told.
// `@deterministic` is a claim the compiler checks, not a note to the reader.
//
// A simulation is given its delta as a parameter rather than reaching out for one. That is what
// makes it a function of its inputs, which is the property a replay depends on.
@deterministic
fn step(position: f32, velocity: f32, dt: f32) -> f32 {
return position + velocity * dt
}
@pure
fn squared(x: f32) -> f32 {
return x * x
}
The annotations are claims, not information
@pure and @deterministic add nothing the compiler needs. They are assertions it checks, and a violation names the call that broke the claim rather than the annotation that made it, because the call is the thing you have to change.
Determinism is the engine rule made mechanical
Inside a deterministic region the compiler refuses a call that reads a wall clock, takes unseeded entropy, reaches a host service, touches renderer state, samples a pointer, or asks a model. That list is the boundary the host engine already draws by convention, expressed as something a build can enforce.
Awaiting a clock and reading one land on opposite sides
Awaiting the fixed step inside a deterministic function is accepted, because a resume point counted in whole ticks is a function of the tick count and replays identically. Reading a delta is refused, because a simulation is given its delta as a parameter and one that reached out for it has stopped being a function of its inputs. Both rules are right and they govern different operations.
// A task is asynchronous behaviour with an owner.
//
// `spawn` starts one inside a `scope`, and leaving the scope cancels whatever has not finished.
// That is the rule that stops detached work outliving the thing that created it, and it is
// structural here rather than something every author has to remember.
//
// `begin` is a task rather than a function, and the compiler insists: a scope opened in a function
// would be left on the same tick it was opened, cancelling everything spawned into it.
data Signal {
fired: bool = false
count: u32 = 0
}
task pulse(signal: mut Signal) {
await fixedTime(250ms)
signal.count = signal.count +| 1
signal.fired = true
}
task begin(signal: mut Signal) {
scope effect {
spawn pulse(signal)
await fixedTime(1s)
}
}
Separate from availability, on purpose
An effect is a property of the code and availability is a property of the target. Keeping them apart is what lets a file be checked for what it does before anything decides whether it may do it here.