Releasing v0.2.0 — Named Receivers and Generic impl Blocks

· 3 min read
v0.2.0

This is the biggest language-level change since actors landed. The self keyword is gone from Hew.

Named receivers replace self. Impl methods now take an explicit, named parameter instead of the implicit self receiver. fn area(self) becomes fn area(r: Rectangle), and field access becomes r.width. The type is always explicit — no guessing what self refers to in nested contexts. Trait definitions use Self (capital S) for the implementing type: fn next(it: Self) -> Option<Self::Item>.

Actors use this for self-reference. Inside actor receive fn handlers and methods, fields are accessed directly — count instead of explicit receiver-qualified field access. When you need to disambiguate (e.g., a parameter shadows a field name), use this.field. The this keyword is only valid inside actor bodies.

impl Display for Point {
    fn display(p: Point) -> String {
        f"({p.x}, {p.y})"
    }
}

actor Counter {
    let count: Int;

    receive fn increment(n: Int) {
        count = count + n;
    }

    receive fn get_count() -> Int {
        count
    }
}

Generic impl<T> blocks. Monomorphization now works through impl blocks — impl<T: Display> Printable for Container<T> generates specialized code for each concrete type. Previously, generic bounds on impl blocks were parsed but silently ignored during codegen.

Trait-bounded method dispatch. fn f<T: Show>(item: T) { item.show() } now resolves the correct show() implementation at compile time through monomorphization. Combined with generic impl blocks, this means Hew’s generics story is now complete for the common cases.

while let patterns. Loop while a pattern matches: while let Some(x) = iter.next() { process(x); }. The loop exits as soon as the pattern fails to match.

Nested destructuring. let (a, (b, c)) = (1, (2, 3)) works in all binding positions — let, var, function parameters, and match arms. Struct patterns too: let Point { x, y } = p destructures a struct into its fields.

Closure return type coercion fixed. Closures that return a subtype now correctly coerce to the expected return type. This was a type checker bug that surfaced when passing closures to higher-order functions like map and filter.

defer blocks fixed. Defer now executes in correct LIFO order and runs even when the enclosing scope exits via early return. Previously, defers could execute out of order or be skipped entirely on non-local exits.

Labelled loops. @label: loop { ... break @label; } breaks out of a specific enclosing loop. Works with for, while, and bare loop. Labelled continue is also supported.

panic() no longer segfaults. The runtime’s panic handler now unwinds correctly instead of dereferencing a null scheduler pointer. Panics print a message and exit with code 101, as intended.

Enum variant constructors. Shape::Circle(5) now works as a constructor expression. Previously, enum variants with payloads could only be created in match arms. Now they work anywhere an expression is valid.

451+ E2E tests (up from 389 in v0.1.9). The new tests cover generics, destructuring patterns, while let, labelled loops, and the self removal migration.

Coverage infrastructure. make coverage-rust, make coverage-codegen, and make coverage-all generate HTML coverage reports using llvm-cov. CI now tracks coverage trends.

Formatter fixes. Seven semantic corruption bugs fixed in hew fmt — cases where the formatter would silently change the meaning of code by rewriting operator precedence, dropping parentheses, or mishandling string interpolation boundaries.

Part 32 covers state machines and duration literals.