Skip to content

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.

counter.zolo
// 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:

main.zolo
// 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.

enespt-br