Generating Code
Single file
# Rust (default target)
vexilc codegen hello.vexil --target rust --output hello.rs
# TypeScript
vexilc codegen hello.vexil --target typescript --output hello.ts
# Go
vexilc codegen hello.vexil --target go --output hello.go
# Python
vexilc codegen hello.vexil --target python --output hello.py
Default target is rust. Output goes to stdout if --output is omitted.
Rust and TypeScript generated paths have broad cross-language byte-vector coverage. Generated Go and Python are verified against a representative shared wire matrix; verify application-specific schemas and environments separately.
Traits are emitted as structural contracts in every target. Portable trait functions become mutable instance methods:
- Rust uses
&mut self. - TypeScript keeps message interfaces and adds a
MessageFieldsinput type pluscreateMessageobject factory. Decode routes through the factory so decoded values carry their methods. - Go uses pointer-receiver methods.
- Python uses dataclass methods and emits static
Protocolconformance proofs underTYPE_CHECKING.
Portable bodies support literals, parameters, immutable let bindings,
self.field, receiver-field assignment, return, and unary/binary operators.
Free calls, method calls, local reassignment, non-receiver assignment, and
target identifier collisions fail code generation before output is produced.
Impl bodies are generated source templates; they do not add runtime dispatch or
wire metadata.
Multi-file project
For schemas with imports, use the build subcommand:
vexilc build root.vexil --include ./schemas --output ./generated --target rust
This resolves all imports, compiles in topological order, and generates one file per namespace.
Project code generation resolves traits imported directly by name, through a
unique wildcard, or through one explicit alias qualifier such as
impl Contracts.Tagged<u64> for Event. A trait reachable only through another
schema's imports is not re-exported: the implementing schema must import it
explicitly. Imported impl declarations remain in their defining schema and are
not copied into consumers.
Watch mode
Auto-rebuild on save:
vexilc watch root.vexil --include ./schemas --output ./generated --target typescript
Changes to any .vexil file in the watched directories trigger a rebuild with 200ms debounce.
Using generated code
Rust
Add vexil-runtime to your Cargo.toml:
[dependencies]
vexil-runtime = "0.5"
#![allow(unused)] fn main() { use vexil_runtime::{BitWriter, BitReader, Pack, Unpack}; let greeting = Greeting { name: "world".to_string(), message: "hello".to_string(), count: 42, _unknown: Vec::new(), }; // Encode let mut w = BitWriter::new(); greeting.pack(&mut w).unwrap(); let bytes = w.finish(); // Decode let mut r = BitReader::new(&bytes); let decoded = Greeting::unpack(&mut r).unwrap(); }
TypeScript
Install @vexil-lang/runtime:
npm install @vexil-lang/runtime
import { BitWriter, BitReader } from '@vexil-lang/runtime';
import { encodeGreeting, decodeGreeting } from './hello';
const w = new BitWriter();
encodeGreeting(
{ name: 'world', message: 'hello', count: 42, _unknown: new Uint8Array(0) },
w,
);
const bytes = w.finish();
const r = new BitReader(bytes);
const decoded = decodeGreeting(r);
Go
import vexil "github.com/vexil-lang/vexil/packages/runtime-go"
greeting := &Greeting{
Name: "world",
Message: "hello",
Count: 42,
}
w := vexil.NewBitWriter()
greeting.Pack(w)
bytes := w.Finish()
r := vexil.NewBitReader(bytes)
var decoded Greeting
decoded.Unpack(r)
The versioned Go runtime module is available as github.com/vexil-lang/vexil/packages/runtime-go@v0.1.1. See the Go runtime page for installation details.
Python
Install the published runtime from PyPI:
python -m pip install vexil-runtime
When testing changes from this checkout, you can instead install
./packages/runtime-py into an isolated environment.
from hello import Greeting
encoded = Greeting(name="world", message="hello", count=42).encode()
decoded = Greeting.decode(encoded)
assert decoded.count == 42
See the support matrix before choosing a production target combination.