███████╗███╗ ██╗ █████╗ ████████╗██╗██╗ ██╗███████╗
╚══███╔╝████╗ ██║██╔══██╗╚══██╔══╝██║██║ ██║██╔════╝
███╔╝ ██╔██╗ ██║███████║ ██║ ██║██║ ██║█████╗
███╔╝ ██║╚██╗██║██╔══██║ ██║ ██║╚██╗ ██╔╝██╔══╝
███████╗██║ ╚████║██║ ██║ ██║ ██║ ╚████╔╝ ███████╗
╚══════╝╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═══╝ ╚══════╝
"A shell plugin that runs at native speed — no interpreter, no re-source."
znative is the stable, versioned C ABI for writing native (Rust) plugins for zshrs — the first JIT-compiled Unix shell, and the first that hosts compiled-language plugins loaded at runtime with no recompile of the shell. A plugin is an ordinary cdylib the shell dlopens via zmodload -R <path>. The host/plugin boundary is #[repr(C)] structs + extern "C" function pointers; nothing about Rust's unstable layout, allocator, or panic ABI crosses it. The matching package manager is the znative builtin (znative load owner/repo).
- [0x00] Overview
- [0x01] Install
- [0x02] Quick start
- [0x03] Host API
- [0x04] Loading & managing
- [0x05] ABI versioning
- [0xFF] License
Shells have two extension models: interpreted script plugins, and native modules bound to the shell's private internal headers (bash enable -f, zsh zmodload) — welded to one build, no stable ABI. znative is a third: a published, versioned ABI package. A third party runs cargo add znative, writes a handler, ships a cdylib, and it loads into any compatible zshrs — native speed, no shell source tree, no recompile, and version-mismatched plugins refused at load rather than crashing.
[lib]
crate-type = ["cdylib"]
[dependencies]
znative = "0.12"use znative::{declare_plugin, Args, Host};
use std::os::raw::c_int;
fn rhello(host: &Host, args: &Args) -> c_int {
host.print(&format!("hello, {}\n", args.rest().join(" ")));
0
}
declare_plugin! {
name: "hello",
version: "0.1.0",
builtins: { "rhello" => rhello },
}$ cargo build
$ zshrs
% zmodload -R target/debug/libhello.dylib # .so on Linux
% rhello world
hello, worldInside a handler, Host is the shell's callback table:
| Method | Purpose |
|---|---|
host.print(s) |
write to the shell's stdout |
host.eval(code) -> i32 |
run shell code, return its exit status |
host.getvar(name) -> Option<String> |
read a shell scalar parameter |
host.setvar(name, value) |
set a shell scalar parameter |
host.getfunction(name) -> Option<String> |
read a shell function's deparsed body (${functions[name]}) |
host.addfunction(name, body) -> bool |
define/replace a shell function (functions[name]=body) |
host.register_builtin(name, fn) |
register a command handler dynamically |
host.add_match(word) |
emit one completion candidate |
host.install_completion(cmd, gen) |
wire a native (Rust) completion into compsys |
Args gives .name() (argv[0]), .rest() (the arguments), and .to_vec() (all of argv). A handler returns the command's exit status (0 = success), like a shell builtin.
getfunction/addfunction are the only structured access to shell functions. Because addfunction then getfunction round-trips a body through the shell's own parser and pretty-printer, the pair doubles as deparse-as-a-service — define a temp function from arbitrary source, read back its normalized form.
A plugin can also ship native (Rust) completions: add a completions: block to declare_plugin! mapping a command to a generator that emits candidates with host.add_match.
Low level (zmodload -R):
zmodload -R <path>... # load
zmodload -R # list loaded plugins
zmodload -uR <name>... # unload by nameFor distribution, the znative package manager builds and loads straight from a repo — one line in .zshrc, self-installing on first start:
znative load MenkeTechnologies/zshrs-forgitABI_VERSION is bumped on any layout/semantic change to the ABI structs. The host refuses to load a plugin whose abi_version does not match its own (a wrong struct layout would be undefined behaviour). Keep this crate's major/minor aligned with your target zshrs release.
MIT. See LICENSE.