Working with Zig Integration
Working with Zig Integration
Section titled “Working with Zig Integration”Use Zig deliberately through explicit Janus/Zig boundaries.
Time: 50 minutes
Level: Intermediate
Prerequisites: Tutorials 1-3 (Hello World, CLI Tool, Error Handling)
What you’ll learn: explicit bridge modules, generated wrappers, allocators, and when use zig is appropriate
Important: this tutorial is being migrated to Janus’ stricter boundary doctrine. The canonical rule is: ordinary
.janapplication code prefersuse std.*, whileuse zigis reserved for explicit bridge modules or compiler-generated wrappers. Treat broaduse zig "std/..."examples below as legacy patterns under replacement, not recommended end-state Janus style.
Why Zig Integration?
Section titled “Why Zig Integration?”Most languages make you choose: clean syntax or systems power. Janus keeps the power, but it now draws a much harder language boundary.
Janus compiles through Zig — not beside it, not to it. That does not mean
ordinary .jan code should directly mirror Zig source. The intended end state
is Janus application code on one side, explicit bridge modules on the other,
with the compiler and toolchain handling the lowering.
The Breakthrough: One Language, Two Faces
Section titled “The Breakthrough: One Language, Two Faces”// Janus do..end blocks wrapping Zig's Ed25519 primitives.// This is NOT FFI. It compiles as one unit.pub fn sign(self: *const SoulKey, msg: []const u8) [64]u8 do const kp = Ed25519.KeyPair .generateDeterministic(self.seed) catch return .{0} ** 64; return kp.sign(msg, null).toBytes();endThis is NOT:
- Foreign Function Interface (FFI)
- C bindings or wrappers
- Code generation or transpilation
This IS:
- Zig’s type system with Janus control flow
- One compilation unit, one binary
- Zero overhead — the same machine code Zig would produce
- Battle-tested crypto, I/O, networking from day one
Step 1: Understanding use zig (10 min)
Section titled “Step 1: Understanding use zig (10 min)”How It Works
Section titled “How It Works”// Import an explicit bridge moduleuse zig "std/bridge/process_bridge.zig"What happens:
- Janus compiler finds the explicit bridge module
- Imports it natively into the same build
- Keeps the foreign implementation isolated at a named boundary
- Type-checks the exposed surface at compile time
Common Zig Modules
Section titled “Common Zig Modules”| Module | Purpose | Example |
|---|---|---|
std/bridge/process_bridge.zig | Process bridge | Execute subprocess work behind a boundary |
std/bridge/json_bridge.zig | JSON bridge | Handle-based JSON parsing behind a boundary |
std/bridge/http_bridge.zig | HTTP/socket bridge | Socket and HTTP primitives behind a boundary |
bridge/net_bridge.zig | Project-local bridge | Isolate project-specific Zig interop |
Step 2: Memory Management (Allocators) (10 min)
Section titled “Step 2: Memory Management (Allocators) (10 min)”The Allocator Pattern
Section titled “The Allocator Pattern”In Janus + Zig, memory is explicit. No hidden allocations.
The Rule: Functions that allocate memory take an Allocator parameter.
use zig "std/ArrayList"
func create_list(allocator: Allocator) ![]i64 do var list = zig.ArrayList(i64).init(allocator) defer list.deinit()
try list.append(1) try list.append(2) try list.append(3)
return try list.toOwnedSlice()end
func main() !void do let allocator = std.heap.page_allocator
let numbers = try create_list(allocator) defer allocator.free(numbers)
for i in 0..<numbers.len do print_int(numbers[i]) print(" ") end println("")endKey Concepts:
Allocator- The type that manages memorystd.heap.page_allocator- General-purpose allocatordefer list.deinit()- Always clean up!toOwnedSlice()- Transfer ownership from list to slice
Common Allocators
Section titled “Common Allocators”// General-purpose (use for most things)let allocator = std.heap.page_allocator
// Arena allocator (fast, batch-free everything at once)var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator)defer arena.deinit()let allocator = arena.allocator()
// Fixed buffer (no heap allocations)var buffer: [1024]u8 = undefinedvar fba = std.heap.FixedBufferAllocator.init(&buffer)let allocator = fba.allocator()Step 3: File System Operations (10 min)
Section titled “Step 3: File System Operations (10 min)”Reading Files
Section titled “Reading Files”use zig "std/fs"
func read_entire_file(path: []const u8, allocator: Allocator) ![]u8 do // Open file let file = try zig.fs.cwd().openFile(path, .{}) defer file.close()
// Read up to 10MB let content = try file.readToEndAlloc(allocator, 10 * 1024 * 1024)
return contentend
func main() !void do let allocator = std.heap.page_allocator
let content = try read_entire_file("README.md", allocator) defer allocator.free(content)
print("File size: ") print_int(content.len) println(" bytes")endWriting Files
Section titled “Writing Files”use zig "std/fs"
func write_log(message: []const u8) !void do // Create/open file (truncate if exists) let file = try zig.fs.cwd().createFile("app.log", .{}) defer file.close()
// Write message try file.writeAll(message)end
func main() !void do try write_log("Application started\n") println("Log written!")endChecking File Existence
Section titled “Checking File Existence”use zig "std/fs"
func file_exists(path: []const u8) -> bool do zig.fs.cwd().access(path, .{}) catch do return false end return trueend
func main() do if file_exists("config.txt") do println("Config found!") else println("Config missing!") endendStep 4: Dynamic Data Structures (10 min)
Section titled “Step 4: Dynamic Data Structures (10 min)”ArrayList (Growable Arrays)
Section titled “ArrayList (Growable Arrays)”use zig "std/ArrayList"
func process_numbers(allocator: Allocator) !void do var numbers = zig.ArrayList(i64).init(allocator) defer numbers.deinit()
// Add items try numbers.append(10) try numbers.append(20) try numbers.append(30)
// Access items print("First: ") print_int(numbers.items[0]) println("")
print("Length: ") print_int(numbers.items.len) println("")
// Iterate println("All numbers:") for i in 0..<numbers.items.len do print(" ") print_int(numbers.items[i]) println("") end
// Remove last let last = numbers.pop() print("Popped: ") print_int(last) println("")end
func main() !void do let allocator = std.heap.page_allocator try process_numbers(allocator)endHashMap (Key-Value Store)
Section titled “HashMap (Key-Value Store)”use zig "std/HashMap"use zig "std/AutoHashMap"
func count_words(text: []const u8, allocator: Allocator) !void do var counts = zig.AutoHashMap([]const u8, i64).init(allocator) defer counts.deinit()
// Simplified word counting (split by spaces) var word_start = 0 var i = 0
while i < text.len do if text[i] == ' ' or text[i] == '\n' do if i > word_start do let word = text[word_start..i] let entry = try counts.getOrPut(word) if entry.found_existing do entry.value_ptr.* = entry.value_ptr.* + 1 else entry.value_ptr.* = 1 end end word_start = i + 1 end i = i + 1 end
// Print results var iter = counts.iterator() while iter.next() do |kv| do print(kv.key_ptr.*) print(": ") print_int(kv.value_ptr.*) println("") endend
func main() !void do let allocator = std.heap.page_allocator let text = "hello world hello janus world" try count_words(text, allocator)endStep 5: System Interfaces (10 min)
Section titled “Step 5: System Interfaces (10 min)”Command-Line Arguments
Section titled “Command-Line Arguments”use zig "std/process"
func main() !void do let allocator = std.heap.page_allocator
// Get arguments let args = try zig.process.argsAlloc(allocator) defer zig.process.argsFree(allocator, args)
println("Program arguments:") for i in 0..<args.len do print(" [") print_int(i) print("] ") println(args[i]) endendRun it:
janus build args.jan -o args./args hello world 123Output:
Program arguments: [0] ./args [1] hello [2] world [3] 123Environment Variables
Section titled “Environment Variables”use zig "std/process"
func get_env(key: []const u8, allocator: Allocator) ![]const u8 do let value = try zig.process.getEnvVarOwned(allocator, key) return valueend
func main() !void do let allocator = std.heap.page_allocator
let home = get_env("HOME", allocator) catch |err| do println("HOME not set") return end defer allocator.free(home)
print("Home directory: ") println(home)endExit Codes
Section titled “Exit Codes”use zig "std/process"
func main() !void do let success = check_preconditions()
if not success do println("Error: Preconditions not met") zig.process.exit(1) // Exit with error code end
println("Success!") zig.process.exit(0)endWhat You Learned
Section titled “What You Learned”Zig Integration:
Section titled “Zig Integration:”use zig "module/path"for native imports- Zero-cost integration (not FFI)
- Type-safe Zig stdlib access
- Compile-time verification
Memory Management:
Section titled “Memory Management:”- Explicit allocators (no hidden allocations)
std.heap.page_allocatorfor general usedeferfor cleanuptoOwnedSlice()for ownership transfer
File System:
Section titled “File System:”std/fsfor file operationsopenFile(),createFile(),readToEndAlloc()- Always use
defer file.close()
Data Structures:
Section titled “Data Structures:”ArrayListfor dynamic arraysHashMap/AutoHashMapfor key-value storagedefer .deinit()for cleanup
System Interfaces:
Section titled “System Interfaces:”std/processfor args, env, exit codesargsAlloc()for command-line argumentsgetEnvVarOwned()for environment variables
Challenges & Extensions
Section titled “Challenges & Extensions”- Write a program that reads a file and counts the number of lines
- Create a simple key-value store that saves to a JSON file
- Build a directory lister that prints all files in the current directory
Medium:
Section titled “Medium:”- Implement a text search tool (like
grep) that finds patterns in files - Create a file backup tool that copies files to a backup directory
- Build a CSV parser that reads tabular data into a HashMap
Advanced:
Section titled “Advanced:”- Design a simple database (key-value store) with persistence
- Implement a log rotation system (delete old logs when they get too large)
- Create a file watcher that monitors changes and triggers actions
- Build a concurrent file processor using Zig’s threading primitives
Real-World Example: Configuration Manager
Section titled “Real-World Example: Configuration Manager”use zig "std/fs"use zig "std/HashMap"use zig "std/AutoHashMap"
struct Config do settings: zig.AutoHashMap([]const u8, []const u8) allocator: Allocatorend
func Config.load(path: []const u8, allocator: Allocator) !Config do var settings = zig.AutoHashMap([]const u8, []const u8).init(allocator)
// Read config file let file = try zig.fs.cwd().openFile(path, .{}) defer file.close()
let content = try file.readToEndAlloc(allocator, 1024 * 1024) defer allocator.free(content)
// Parse simple key=value format var line_start = 0 for i in 0..<content.len do if content[i] == '\n' do let line = content[line_start..i] if line.len > 0 do // Find '=' separator for j in 0..<line.len do if line[j] == '=' do let key = line[0..j] let value = line[j+1..line.len] try settings.put(key, value) break end end end line_start = i + 1 end end
return Config { .settings = settings, .allocator = allocator }end
func Config.deinit(self: *Config) do self.settings.deinit()end
func Config.get(self: *Config, key: []const u8) -> ?[]const u8 do return self.settings.get(key)end
func main() !void do let allocator = std.heap.page_allocator
var config = try Config.load("app.config", allocator) defer config.deinit()
if config.get("debug") do |value| do print("Debug mode: ") println(value) end
if config.get("port") do |value| do print("Port: ") println(value) endendNext Steps
Section titled “Next Steps”You’re ready for production!
Section titled “You’re ready for production!”You now know:
- How to write clean Janus code ([Tutorial 1]/tutorials/hello-to-production/)
- How to build CLI tools ([Tutorial 2]/tutorials/cli-tool/)
- How to handle errors safely ([Tutorial 3]/tutorials/error-handling/)
- How to leverage Zig’s stdlib (Tutorial 4)
Continue Learning:
Section titled “Continue Learning:”- Explore the [Error Handling Doctrine]/reference/errors/
- Read the [ASTDB Architecture]/architecture/astdb/
- Study the Zig Standard Library
- Join the community (coming soon!)
Congratulations! You’re now a Janus + Zig power user!
Build something amazing with your new superpowers!