Releasing v0.2.1 — One Binary, Hardened Runtime

· 4 min read
v0.2.1

No new syntax in this release. This one is about making the runtime actually reliable and making the toolchain simpler to install.

The compiler is one binary now. hew-codegen was a separate C++ executable that the hew binary shelled out to for MLIR lowering and LLVM code generation. That’s gone. The MLIR/LLVM backend is now statically linked into hew itself via build.rs — cmake configures and compiles the C++ codegen as a static library, and Cargo links it in. The result: hew, adze, and hew-lsp are the only binaries shipped. No more coordinating versions between two executables, no more hew-codegen requiring zstd and libc++ at runtime. On macOS, the hew binary links only against system frameworks — it’s a single file you can drop anywhere.

Runtime hardening. This was the bulk of the release — 30+ fixes across the runtime. The short version: we went looking for every place the runtime could panic, deadlock, leak memory, or corrupt state, and fixed them. Some highlights:

  • Use-after-free in actor cleanup — the crash handler was reading actor metadata after the actor’s memory had been freed. The fix caches the data before entering the supervisor trap.
  • Data races on task stateHewTask.state was being read and written from multiple threads without synchronization. Now it’s atomic.
  • Lock poisoning — every Mutex and RwLock in the runtime used .unwrap() on lock acquisition, meaning any panic in a critical section would cascade into panics everywhere else. All locks now use poison-recovery: if a thread panicked while holding a lock, other threads recover the lock state instead of panicking themselves.
  • Shutdown deadlocks — the profiler thread, timer wheel, and socket readers could all deadlock during shutdown if they tried to acquire resources that were being torn down. Shutdown now follows a strict ordering: stop the scheduler, cancel timers, drain connections, then free resources.
  • Noise crypto cleanup — handshake buffers are now zeroized after use, and the handshake returns the full keypair so the caller doesn’t have to reconstruct it from partial state.

The pattern across all of these: the runtime was written for the happy path, and the unhappy paths (panics, shutdown, resource exhaustion) were afterthoughts. This release makes the unhappy paths first-class.

Generic channels. Channel<T> now works with String and int types, not just the default message type. try_recv returns Option<T> instead of a sentinel value, which means you can distinguish “no message” from “message with a zero/empty value.” Stream and Sink types are monomorphized for String and bytes.

Actor terminate blocks. Actors can now declare cleanup logic that runs when they stop:

actor ResourceHolder {
    let handle: Int;

    receive fn work() { /* ... */ }

    terminate {
        close_handle(handle);
    }
}

The terminate block runs regardless of whether the actor stopped normally, was killed by a supervisor, or crashed. It’s the actor equivalent of defer.

Semantic tokens. The LSP now classifies function calls as function tokens (not variable) and type annotations as type tokens. If your editor theme distinguishes function calls from variable references, Hew code now highlights correctly.

Build system fixes. The Makefile was segfaulting on macOS due to a recursive rwildcard macro that overflowed GNU Make 3.81’s stack. (Yes, macOS still ships Make from 2006.) Also fixed: the build.rs auto-detection of LLVM on macOS via Homebrew, and the Homebrew formula itself, which referenced library paths that hadn’t existed since the codegen was embedded.

562 E2E tests (up from 451 in v0.2.0). New coverage includes wire protocol, file I/O, timers, routing tables, crypto, generators, HTTP, iterators, streams, DateTime, JWT, and JSON/YAML/TOML encoding. The C++ codegen layer also got its first unit tests.

LIVE_ACTORS HashMap. The scheduler’s actor lookup was a linear scan over a Vec. Now it’s a HashMap. This doesn’t matter at 10 actors; it starts to matter at 10,000.