Plugins nativos y stdlib
En esta página
Los plugins nativos de Zolo (escritos en Rust y compilados como cdylib) tienen
sintaxis dedicada de importación. Prefiere una lista con nombres como
use plugin binary::{BinaryReader, parse_csv_text} y llama directamente a las
funciones libres importadas.
El wildcard registra todos los símbolos exportados como globales. Es útil para un prelude deliberado, pero una lista con nombres deja las dependencias más claras:
use plugin binary::* expone BinaryReader y BinaryWriter como globales.
Útil cuando quieres toda la API del plugin sin prefijo.
// Feature: wildcard `use ...::*` — imports all public names
// Syntax: `use plugin foo::*` (native plugins) or `use foo::*` (modules)
// When to use: the canonical case is importing every symbol from a
// native Zolo plugin (`binary`, `winit`, `wgpu`, etc.) when you want
// the entire API available without prefix. For typical user-land
// `.zolo` modules, prefer a named list `{a, b, c}` to avoid polluting
// the scope.
use std::string
// The `binary` plugin registers BinaryReader, BinaryWriter and helpers
// as classes. `use plugin binary::*` opts in to "register everything
// as globals" — the same shape the plugin's runtime publishes.
use plugin binary::*
fn main() {
// 4 little-endian bytes = u32 = 7
let raw = string::char(0x07, 0x00, 0x00, 0x00)
// BinaryReader came from the prelude — no need to qualify.
let r = BinaryReader.from_bytes(raw)
print(r.read_u32_le())
// expected: 7
}
La forma avanzada use plugin foo crea un namespace (foo.Window,
foo.EventLoop, ...). Añadir {self, member} a una lista con nombres vincula
el namespace y un miembro directo al mismo tiempo:
use plugin winit crea winit.Window; use plugin crypto::{self, hash} crea
tanto crypto.hash(...) como el atajo directo hash(...).
// Feature: `use plugin <name>` — explicit native-plugin loader
//
// Five supported forms:
//
// use plugin foo → namespaced: load + move every
// symbol the plugin registers
// into a `foo` namespace table.
// Globals are kept clean.
// use plugin foo::* → opt-in glob: every symbol the
// plugin registers becomes a global.
// use plugin foo::Bar → load + `local Bar = foo.Bar`
// use plugin foo::{a, b} → load + per-member locals
// use plugin foo::{self, a} → also bind the plugin namespace itself
//
// Bare `use plugin foo` works the same way regardless of whether the
// plugin's runtime registers `<name>` as a unified namespace or as
// individual classes (Window, EventLoop, …) — the loader bridges both
// into the namespace table.
use std::crypto
use plugin winit // namespace — winit.Window, winit.EventLoop
use plugin wgpu // namespace — wgpu.*
use plugin crypto::{self, hash} // namespace + direct hash() binding
fn main() {
// Bare `use plugin foo` keeps the namespace; nothing leaks to
// global scope. Members live under the plugin's name.
print("winit.Window in scope: {winit.Window != nil}") // true
print("winit.EventLoop in scope: {winit.EventLoop != nil}") // true
print("wgpu in scope: {wgpu != nil}") // true
// `crypto` is bound by the `self` entry; `hash` by the explicit
// member entry. Both forms reach the same underlying function.
let digest_a = hash("sha256", "zolo") // direct binding
let digest_b = crypto.hash("sha256", "zolo") // via namespace
print("hash(\"sha256\", \"zolo\") direct = {digest_a}")
print("crypto.hash(...) = {digest_b}")
print("digests match: {digest_a == digest_b}")
}
Carga el plugin binary como namespace y accede a funciones libres, clases,
métodos estáticos y métodos de instancia mediante binary.
// Feature: bare `use plugin <name>` — the namespaced loader form.
//
// Bare `use plugin binary` loads the native `binary` cdylib AND builds a
// `binary` namespace table whose fields are every symbol the plugin
// registers (the `BinaryReader` class, the `parse_csv_text` free fn). The
// members live under `binary.*`; nothing leaks to the global scope.
//
// This is the light, deterministic sibling of `10-use-plugin.zolo` (which
// exercises the same namespace mechanism against the heavier winit/wgpu
// plugins). Backend parity target: VM = native = LLVM.
//
// Byte source is a plain ASCII string literal so the case exercises ONLY the
// plugin namespace path — no dependency on `std::string::char`, which is not
// yet bridged on the native/LLVM backends.
use plugin binary // namespace — binary.BinaryReader, binary.parse_csv_text
fn main() {
// The plugin's name resolves to the namespace table, not nil.
print("binary in scope: {binary != nil}")
print("binary.BinaryReader: {binary.BinaryReader != nil}")
print("binary.parse_csv_text: {binary.parse_csv_text != nil}")
// Invoke a free function THROUGH the namespace and index the nested tables
// returned by the plugin through Zolo's 0-based array syntax.
let rows = binary.parse_csv_text(
"kind, value\nplanet, 42\n# ignored",
",",
"#"
)
print("csv rows: {rows.len()}")
print("csv header: {rows[0][0]}={rows[0][1]}")
print("csv data: {rows[1][0]}={rows[1][1]}")
// Static-method + instance-method dispatch reached THROUGH the namespace:
// read the first byte of "A" (0x41 = 65).
let r = binary.BinaryReader.from_bytes("ABC")
print(r.read_u8())
// expected: 65
}
Los fallos de plugins usan el canal normal de panic de Zolo. Detienen los callbacks de orden superior, ejecutan la limpieza diferida y se pueden capturar:
Captura un error de plugin dentro de map y conserva el fallo original cuando
la limpieza diferida también produce panic.
// Regression: native/LLVM plugin errors use the ordinary Zolo panic channel.
// No placeholder nil may leak into the rest of the expression, HOF callbacks
// stop immediately, and defer cleanup runs completely before catch_panic sees
// the original error.
use plugin binary
fn cleanup() {
print("cleanup A")
let inner = try {
print("cleanup try")
7
} catch e {
print("unexpected cleanup catch")
-1
}
print("cleanup B {inner}")
}
fn failing_callback(value) {
print("callback before {value}")
binary.parse_csv_text("x", "", "")
print("UNREACHABLE callback after")
return value
}
fn fail_inside_hof() {
defer cleanup()
let values = [1, 2]
let mapped = values.map(failing_callback)
print("UNREACHABLE hof after {mapped.len()}")
}
fn fail_with_secondary_cleanup() {
defer print("remaining cleanup")
defer {
print("secondary cleanup before")
panic("secondary cleanup failure")
print("UNREACHABLE secondary cleanup after")
}
binary.parse_csv_text("x", "", "")
}
fn main() {
let caught = catch_panic(fail_inside_hof)
print("caught={caught.ok}")
print("after catch")
let secondary = catch_panic(fail_with_secondary_cleanup)
print("secondary caught={secondary.ok}")
let kept_original = secondary.error.contains("binary: parse_csv_text empty separator")
let replaced_by_secondary = secondary.error.contains("secondary cleanup failure")
print("secondary kept original={kept_original}")
print("secondary replaced original={replaced_by_secondary}")
print("after secondary catch")
}
// expected, identically on VM/native/LLVM:
// callback before 1
// cleanup A
// cleanup try
// cleanup B 7
// caught=false
// after catch
// secondary cleanup before
// remaining cleanup
// secondary caught=false
// secondary kept original=true
// secondary replaced original=false
// after secondary catch
Para la biblioteca estándar, puedes usar la ruta completamente calificada
std::modulo::funcion() sin ningún use — ideal para llamadas puntuales donde
importar el módulo entero añadiría ruido:
std::os::clock() funciona sin use std::os. Si llamas a os.clock() con
frecuencia, vale la pena hacer use std::os y usar la forma corta.
// Feature: fully-qualified `std::` paths without `use`
// Syntax: `std::os::clock()`, `std::math::floor(x)` — no `use` needed
// When to use: a one-off stdlib call where importing the whole module
// would be noise. Reach for `use std::M` when you call `M.foo` a lot.
// The fully-qualified form resolves on its own — note: NO `use std::os`.
let start = std::os::clock()
var count = 0
for _ in 0..100_000 {
count += 1
}
print("count =", count)
print("elapsed =", std::os::clock() - start)
// Contrast — the SHORT form still requires the import:
//
// use std::os // <- required for the line below
// let t = os.clock() // TE105 without the `use`
//
// So both of these reach the same function:
// std::os::clock() // qualified — works with or without `use`
// os.clock() // short — needs `use std::os`
//
// How it works: a compiler pass (std_path_desugar) rewrites every
// `std::M::...` value path into the same member-access the parser emits
// for `M....`, and injects a synthetic `use std::M`. So typeck (TE105),
// effect-checking, the unused-import lint, and lowering all treat the
// qualified form exactly like the imported short form. The formatter
// keeps `std::os::clock()` verbatim — it round-trips unchanged.
//
// expected:
// count = 100000
// elapsed = <small float>
Consulta también