State Preservation
When the VM swaps a module it merges the new exports over the existing table. For non-function values, if the runtime type of the live copy matches that of the new initializer, the live copy is kept and the new literal is discarded. This is how a counter keeps growing even after you edit the function that prints it.
The counter module keeps internal state in state, a map with a count field.
To correctly increment the value that survives across swaps, writes must go
through package.loaded["counter"] — never through a local capture, which
points to the old copy after the swap.
Edit only the print format; the number keeps growing without resetting.
// Feature: state preserved by type on hot-reload
// Syntax: `pub let counter = 0` — literal initializer.
// When to use: we want to edit the fn but keep the accumulated value.
//
// Critical pattern: to mutate the preserved value, write via
// `package.loaded["counter"]`. Mutations through a `local` captured
// inside the closure only affect the old version of the module after swap.
use std::package
let state = #{count: 0}
pub fn bump() {
let live = package.loaded["counter"]
// Edit the string below while the program runs — the number
// keeps growing, it does NOT reset to 0.
live.state.count = live.state.count + 1
print("Count: {live.state.count}")
}
Requires the Zolo CLI/host — open in the playground or run locally.
The entry point simply calls bump() on each ENTER, with no knowledge of the
preservation mechanism:
// Feature: state lives across reloads
// Syntax: `pub let` with a stable runtime type is preserved.
// When to use: counters, caches, sessions — anything you want
// to keep "alive" across edits.
use counter::{bump}
fn main() {
print("ENTER increments the counter. 'q' to quit.")
print("Edit counter.zolo (e.g. the print format) — the value persists.")
while true {
let line = io::read("*l")
if line is nil || line == "q" {
break
}
bump()
}
}
Requires the Zolo CLI/host — open in the playground or run locally.
Challenge
Add a second field max_seen: int = 0 to the state map in counter.zolo
and update bump() to record it. Edit and save several times: confirm that
max_seen is preserved across reloads just like count.