zig-pkl

Migrating from pkl-zig

pkl-zig and zig-pkl both talk to pkl server, so the concepts carry over. The names and ownership rules differ. This page maps the API documented in pkl-zig's README to zig-pkl.

Toolchain and package

pkl-zigzig-pkl
Zig0.16.00.17.0
Pkl0.32.10.25.3 and newer; 0.32.2 for code generation
Dependencypkl_zig, module pklzig_pkl, module zig_pkl
const zig_pkl = b.dependency("zig_pkl", .{ .target = target, .optimize = optimize });
exe.root_module.addImport("zig_pkl", zig_pkl.module("zig_pkl"));

Evaluators

pkl-zig's Evaluator.init starts a server per evaluator unless you use EvaluatorManager. In zig-pkl every evaluator belongs to a Manager, which owns the server:

pkl-zigzig-pkl
pkl.Evaluator.init(io, gpa, .{ .pkl_argv = argv, ... })Manager.init(gpa, io, argv, log), then manager.createEvaluator(options, &rejected)
pkl.EvaluatorManager.init and manager.newEvaluator(options)Manager.init and manager.createEvaluator(options, &rejected)
InitResult.failed with a diagnosticerror.Rejected, with the diagnostic in rejected
evaluator.deinit()evaluator.close(); the manager frees it
Evaluator.initPreconfigured(io, gpa, environ_map)Options.preconfigured(); set env and cache_dir yourself
EvaluatorOptionsBuilder and addModuleReaderBuilder.restricted(allocator) or Builder.preconfigured(allocator), then moduleReaders(&readers)
ModuleSource.fromText(text)Source.text(text)
ModuleSource.fromUri(uri)Source.uri(uri)

pkl-zig's initPreconfigured forwards the process environment and reads PKL_EXEC. zig-pkl does neither on its own: pass the server command to Manager.init, and copy any environment variables a module needs into Options.env. See Security.

Calls on one pkl-zig evaluator are serialized. A zig-pkl evaluator accepts concurrent calls from any thread; see Concurrency.

Loading values

pkl-zigzig-pkl
evaluator.loadFromPath(T, path)evaluator.evaluateInto(T, allocator, .uri(try zig_pkl.uri.fromFile(allocator, io, path)), null, &failure)
evaluator.evaluateModule(T, source)evaluator.evaluateInto(T, allocator, source, null, &failure)
evaluator.evaluateExpressionevaluator.evaluateInto(T, allocator, source, expression, &failure)
evaluator.evaluateOutputText(source)evaluator.evaluateSource(source, "output.text", &failure)
evaluator.evaluateOutputBytes(source)evaluator.evaluateSource(source, "output.bytes", &failure)
evaluator.evaluateOutputValue(T, source)evaluator.evaluateInto(T, allocator, source, "output.value", &failure)
evaluator.evaluateOutputFiles(source)evaluator.evaluateSource(source, "output.files.toMap().mapValues((_, f) -> f.text)", &failure)
pkl.deinit(T, allocator, &value)zig_pkl.convert.freeInto(T, allocator, value)
pkl.Valuezig_pkl.Value inside a value.Tree; free with tree.deinit()

Evaluation errors return error.EvaluationFailed with Pkl's message in failure. See Errors.

Readers and logging

Both libraries take module and resource readers with a scheme, read, and list. In zig-pkl, read and list return memory allocated with the allocator they receive, and the manager frees it after replying. See Readers. Loggers are set per evaluator through Options.logger; see Logging. zig-pkl adds a client for external readers.

Code generation

pkl-zig generates with pkl run codegen/src/gen.pkl or pkl_zig.addCodegen in build.zig. zig-pkl ships the zig-pkl-gen executable and its own @zig annotation package; generated modules have decode, deinit, and load(allocator, evaluator, source, &failure). Heterogeneous unions and unions across packages become tagged unions instead of falling back to a dynamic value. See Code generation.

Low-level protocol

pkl-zig's Transport corresponds to zig_pkl.Backend and zig_pkl.Subprocess, with messages in zig_pkl.protocol and MessagePack in zig_pkl.msgpack. Manager.initWithBackend runs the manager over any Backend.