Saltar al contenido

Clases de Almacenamiento y Descarte

En esta página

Las clases de almacenamiento modifican cuándo y cómo se inicializa un var (o let), sin cambiar la sintaxis de lectura y escritura posterior. Solo están permitidas a nivel de módulo — el typechecker las rechaza dentro de funciones.

var<lazy> — la expresión inicial se ejecuta solo en el primer acceso, no en la línea de declaración. Úsala para valores costosos que quizás nunca sean necesarios:

var<lazy> aplaza la init; let<lazy> hace lo mismo pero es inmutable tras la inicialización.

12-var-lazy.zolo
Playground
// Feature: `var<lazy>` — initializer runs on the FIRST READ, exactly once

// Syntax: `var<lazy> name = expensive_init()`

// When to use: expensive globals you do not always need —

// shaders, parsed configs, large computations, connection pools.

// Skips the work entirely if nothing ever reads the binding.

// Top-level only (lazy is rejected inside functions by the typeck).


// `teste(1)` does NOT run at the declaration line.

fn teste(cur) {
  print("current = {cur}")
}

var<lazy> a = teste(1)
teste(2)        // runs first → "current = 2"

print(a)        // forces lazy init → "current = 1", then prints nil


// Ordering proof: with eager `var`, output would be 1, 2, nil.

// With `var<lazy>`, output is 2, 1, nil — init is deferred until

// the first read of `a`, so `teste(2)` reaches the screen before it.


// Use `let<lazy>` for the more common case: lazy AND immutable

// after init (lazy_static / lazy val / lazy_global pattern).

let<lazy> heavy = compute_pi(1000000)
fn compute_pi(n) {
  print("computing pi to {n} digits...")
  return 3.14159  // pretend this is expensive

}
print(heavy)  // forces init → "computing pi...", then prints 3.14159

print(heavy)  // already initialized → just prints 3.14159


// `var<lazy>` is mutable AFTER init. Direct writes also mark it as

// initialized, so reads after a write skip the init entirely.

var<lazy> cache = expensive_default()
fn expensive_default() {
  print("default never reached")
  return 0
}
cache = 99       // assigns and marks done — init never runs

print(cache)    // 99 (no "default never reached")

var<persistent> — el valor sobrevive al hot reload durante zolo dev. En la primera ejecución la init se ejecuta normalmente; en las recargas siguientes el runtime restaura el valor guardado, ignorando la expresión inicial:

player_pos y high_score continúan desde donde se quedaron tras cada edición/recarga.

13-var-persistent.zolo
Playground
// Feature: `var<persistent>` — value survives plugin / runtime hot reload

// Syntax: `var<persistent> name = init`

// When to use: state you want to keep "alive" across edits in

// `zolo dev` — game state, scores, session data, caches.

// Top-level only (persistent is rejected inside functions).


// On the FIRST run of the program, init runs and the value is stored

// in the runtime's persistent registry (per-module key).

var<persistent> player_pos = 0
var<persistent> high_score = 0

fn step(d) {
  player_pos = player_pos + d
  if player_pos > high_score {
    high_score = player_pos
  }
  print("pos={player_pos} hi={high_score}")
}

step(10)   // pos=10 hi=10

step(15)   // pos=25 hi=25

step(-5)   // pos=20 hi=25


// On subsequent runs (under `zolo dev` after editing this file), the

// init is SKIPPED — `player_pos` and `high_score` start at their last

// values, not at 0. This works because the registry lives on the VM

// state, outside the GC heap of any module, so hot_swap_module never

// touches it.

//

// Reads of a persistent binding cost the same as a plain local

// (1 opcode) — the registry is consulted only at decl and on writes

// (write-through keeps the registry in sync).


// Compose with arithmetic the same as plain bindings.

var<persistent> tick_count = 0
tick_count += 1
print("tick = {tick_count}")

// Persistence is keyed by (module_path, name, type_hash). Renaming a

// binding or changing its declared type invalidates the key — init

// runs from scratch and the old value becomes orphan in the registry.

//

// Two different modules with `var<persistent> counter = 0` get

// DIFFERENT keys (module path is part of the key) — no silent sharing.

//

// Persistence is in-memory only (survives hot reload, not process

// restart). Disk persistence is future work.


// Combine `<lazy>` and `<persistent>` for "expensive init that also

// survives reload" — see 14-var-lazy-persistent.zolo.

Las dos clases se combinan: var<lazy, persistent> hace que la init sea aplazada Y preservada entre recargas — el cálculo costoso se ejecuta exactamente una vez por sesión de desarrollo:

generate_world() se ejecuta una vez; las recargas siguientes usan el valor almacenado.

14-var-lazy-persistent.zolo
Playground
// Feature: `var<lazy, persistent>` — combo: lazy init AND survives reload

// Syntax: `var<lazy, persistent> name = expensive_init()`

// When to use: heavy init you want both deferred AND preserved

// across hot reload — generated worlds, parsed model files,

// pre-warmed caches. Best of both classes.

// Top-level only.


// On first run, registry is empty:

//   - `done` starts false.

//   - First read fires the init.

//   - Init writes the value to the registry.

//

// On subsequent runs (after hot reload):

//   - Registry has the value → `done` starts true (via try_get).

//   - Reads short-circuit; init NEVER fires.

//   - The "expensive" work is paid exactly once across the whole

//     editing session, not per reload.


// Persistent requires a POD-like type — primitives, or structs/tuples

// /arrays whose fields are all POD-like. Maps and types with external

// handles (File, Socket, GpuBuffer) are rejected by the typecheck

// (E_PersistentTypeInvalid). Define an explicit struct here.

struct World {
  width: int,
  height: int,
  seed: int,
}

fn generate_world() {
  print("=== generating world (this is the expensive bit) ===")
  return World { width: 1024, height: 1024, seed: 42 }
}

var<lazy, persistent> world = generate_world()

// At this point: `world` not yet read, registry empty → init NOT fired.


print("about to use world for the first time")
print("world.seed = {world.seed}")

//   ^^^^^ THIS read forces the init.

//   Output: "=== generating world ... ===" then "world.seed = 42"


// Subsequent reads use the cached value.

print("world.seed = {world.seed}")  // just "world.seed = 42", no init log


// Storage class ordering does not matter — these are equivalent:

//   var<lazy, persistent> x = ...

//   var<persistent, lazy> x = ...


// On reload (under `zolo dev`):

//   - Registry already has the world value.

//   - First read of `world` after reload short-circuits to the cached

//     value — `generate_world()` does NOT run again.

//

// Equivalent for IMMUTABLE-after-init: `let<lazy, persistent>`. Use

// when the value should not be reassigned (the typical case).

//

// Forward refs work cleanly: the typeck pre-infers `now()`'s return

// type from its body before checking this binding (Pass 1.75), so

// the POD check sees `int`, not `Any`. No `-> int` needed; order

// independent.

let<lazy, persistent> startup_time = now()

fn now() {
  return 1747000000  // pretend timestamp

}

print("startup = {startup_time}")  // first read → fetches/inits

print("startup = {startup_time}")  // cached

var<atomic> — declara primitivos (int, bool) con semántica de acceso atómico. En el backend Lua actual es una anotación estructural sin efecto observable (VM monohilo); en el backend nativo Cranelift emitirá intrínsecas atómicas reales sin ningún cambio en el código fuente:

var<atomic> hits: int — declaración idéntica a var, pero con garantías futuras de thread-safety.

15-var-atomic.zolo
Playground
// Feature: `var<atomic>` — atomic load/store for thread-safe primitives

// Syntax: `var<atomic> counter = 0`

// When to use: shared primitive state read/written from multiple

// threads. Restricted to word-sized primitives: int, bool, ptr, ref.

// Atomic on `let` is rejected (atomic implies mutability).


// In the Lua VM backend (the default runtime today), `<atomic>` is a

// STRUCTURAL NO-OP — emission is identical to plain `var`. Justification:

//   - Each LuaState is single-threaded by design.

//   - Cross-thread access via plugin bridge is already serialized by

//     `Mutex<LuaState>`.

//   - Adding atomic wrappers would protect nothing.

//

// In the Cranelift native backend (Plan 3, future work), `<atomic>` will

// emit real atomic intrinsics with `seq_cst` ordering. Programs that

// declare `var<atomic>` today will gain real atomicity automatically

// when running on the native backend, with no source changes required.


// Declaration is identical to plain `var` from the user's perspective.

var<atomic> hits = 0
hits += 1
hits += 1
hits += 1
print(hits)  // 3


var<atomic> active: bool = false
active = true
print(active)  // true


// Type restrictions enforced by the typeck:

//

// var<atomic> name = "Zolo"

//                    ^^^^^^ E_AtomicTypeInvalid: `atomic` requires

//                    a word-sized primitive (int, bool); got `str`.

//

// let<atomic> x = 0

//     ^^^^^^ E_AtomicRequiresVar: `atomic` requires `var`, not `let`.


// Compound op restriction (typeck):

//   var<atomic> n = 0

//   n *= 2  // E_AtomicCompoundOp — no native atomic_fetch_mul on most archs

//

// Allowed: =, +=, -=, ++, --

// (When `|=`, `&=`, `^=` are added to the language grammar, they will

// be allowed too — they map to atomic_fetch_or/and/xor.)


// `<atomic>` composes with the other classes; in those combos atomic

// remains structurally inert in Lua but gains semantics on native.

//   var<atomic, persistent> total_kills = 0   // see 13 + 15

//   var<atomic, lazy> conn = make_pool()       // see 12 + 15

Por último, _ = expr es la asignación fantasma (phony): evalúa la expresión (ejecutando sus efectos secundarios) y descarta el resultado inmediatamente. Esto difiere de let _ = expr, que crea un binding invisible con tiempo de vida hasta el final del bloque:

_ = double(5) descarta el valor de retorno; _ nunca se convierte en un nombre legible después.

16-phony-discard.zolo
Playground
// Feature: phony assignment — `_ = expr`

// Syntax: `_ = <expression>`

// When to use: evaluate an expression for its side effects (or to satisfy

// a `@must_use` constraint in the future) while explicitly discarding the

// returned value. `_` is the discard wildcard — it is never a usable

// binding, so reading it later is a compile-time error.

//

// See `specs/phony-assignment.md` for the full specification.


fn double(x: int) -> int {
  x * 2
}

fn make_list() -> [int] {
  [1, 2, 3]
}

// Discard the return value of a function call. `double(5)` is fully

// evaluated (its side effects, if any, run); only its result is dropped.


// Works for any expression type — string, list, arithmetic.

_ = double(5)
_ = make_list()
_ = "ignored string"

_ = 42 + 8
print("phony works at top level")

// expected: phony works at top level


// Phony also works inside function bodies.

fn run() {
  _ = double(10)
  _ = double(20)
  _ = make_list()
  print("phony works inside fn")
}

run()
// expected: phony works inside fn


// Restrictions enforced by the type checker:

//

// 1. `_` is NOT a usable binding — reading it is `E_UnderscoreNotBinding`.

//

//    _ = 42

//    print(_)

//    //    ^ error[E_UnderscoreNotBinding]: `_` is not a usable binding

//                (use a real name to keep the value)

//

// 2. Only `=` is allowed. Compound ops on `_` are `E_PhonyCompoundOp`:

//

//    _ += 1

//    // error[E_PhonyCompoundOp]: phony assignment only supports `=`

//    //                          (not compound ops)

//

// 3. `_` as a PATTERN (in `match`, destructuring) is unchanged — this

//    spec only touches `_` as the LHS of `=` in statement position.

//

//    let (_, b) = (1, 2)        // OK — pattern usage

//    match x { _ => "default" } // OK — pattern usage

//

// 4. Difference vs. `let _ = expr`: phony discards IMMEDIATELY (no

//    binding created, no lexical lifetime). `let _ = expr` creates an

//    invisible local that survives to end of scope — relevant for types

//    with `Drop` semantics like file handles or lock guards.

Buscar en Zolo

9 resultados

enespt-br