Skip to main content

Language Features

Examples and implementation notes for the language surface in v0.9.3.

Actor Model

The chat-room actor keeps its member and message counts in actor fields. Its receive methods are the only code that can change those values, and each call returns a typed result to the caller.

chat_room.hew
actor ChatRoom {
    var members: i64 = 0;
    var message_count: i64 = 0;
 
    receive fn join(user_id: i64) -> i64 {
        members = members + 1;
        members
    }
 
    receive fn post_message(content: string) -> i64 {
        message_count = message_count + 1;
        message_count
    }
}

Memory Model

Ordinary owned values release recursively. Affine resources keep one cleanup responsibility: #[resource] supplies safe scope-exit cleanup, while #[linear] requires an explicit consuming action on every path.

Supervision Trees

The service-cluster example declares three children, a one_for_one strategy, and a budget of five restarts in sixty seconds. Each child also has its own permanent or transient restart policy.

service_cluster.hew
// A supervisor declares its restart strategy, its restart budget
// (`intensity: N within <window>`), and its children. Each child names the
// actor type and its init args; a per-child `restart:` policy decides whether
// a crash is restarted (permanent), restarted only on failure (transient),
// or never (temporary).
actor DatabasePool {
    let pool_size: i64;
    receive fn query() -> i64 {
        pool_size
    }
}
 
actor CacheLayer {
    let capacity: i64;
    receive fn get() -> i64 {
        capacity
    }
}
 
actor ApiHandler {
    let port: i64;
    receive fn handle() {}
}
 
supervisor ServiceCluster {
    strategy: one_for_one;
    intensity: 5 within 60s;
 
    child db: DatabasePool { pool_size: 16 } restart: permanent;
    child cache: CacheLayer { capacity: 1024 } restart: permanent;
    child api: ApiHandler { port: 8080 } restart: transient;
}

Structured Concurrency

The batch starts three independent jobs with fork, then collects their results with await. The scope keeps that child work within the batch before it returns.

batch.hew
// Structured concurrency: a `scope` block opens a region that joins all
// child work before it returns. Start independent jobs with `fork`, then
// collect each result with `await`.
fn process_batch(a: i64, b: i64, c: i64) -> i64 {
    var total = 0;
    scope {
        let ra = fork run_job(a);
        let rb = fork run_job(b);
        let rc = fork run_job(c);
        let first = await ra;
        let second = await rb;
        let third = await rc;
        total = first + second + third;
    };
    total
}
 
fn run_job(job: i64) -> i64 {
    job * 2
}

Concurrency Patterns

The example races two actor calls with select and an after timeout, then uses await fork (...) to start two independent calls and collect both replies.

patterns.hew
actor Fetcher {
    receive fn fetch(key: i64) -> i64 {
        key
    }
}
 
actor Store {
    receive fn get_users() -> i64 {
        0
    }
    receive fn get_posts() -> i64 {
        0
    }
}
 
// Wait for the first responder, with a timeout fallback. Each arm races an
// actor ask; the first to reply wins and the rest are cancelled.
fn race_fetch(primary: Fetcher, fallback: Fetcher, key: i64) -> i64 {
    select {
        resp from primary.fetch(key) => resp handle e { -1 },
        resp from fallback.fetch(key) => resp handle e { -1 },
        after 500ms => -1,
    }
}
 
// Run independent calls concurrently and collect every result.
fn parallel_fetch(db: Store) -> (i64, i64) {
    let (users, posts) = await fork (db.get_users(), db.get_posts());
    (users.expect("store stopped"), posts.expect("store stopped"))
}
 
fn main() {
    let primary = spawn Fetcher;
    let fallback = spawn Fetcher;
    println(race_fetch(primary, fallback, 42));
    let db = spawn Store;
    let (users, posts) = parallel_fetch(db);
    println(f"{users} {posts}");
}

State Machines

The connection machine declares its events, states, guarded transitions, and default behavior in one table. Calling step applies only a transition declared for the current state and event.

connection.hew
// A connection lifecycle as a compiler-checked state machine.
// Events are declared up front; transitions route with `=>` and a body
// that constructs the target state. Every (state, event) pair must be
// covered — `default { state }` makes the rest a no-op stay.
machine Connection {
    events {
        Connect { host: string; }
        Disconnect;
        HeartbeatTimeout;
    }
 
    state Idle;
    state Connecting { host: string; }
    state Connected { host: string; }
    state Disconnected { retries: i64; }
 
    on Connect { host }: Idle => Connecting { host: host }
    on Connect { host }: Disconnected => Connecting when state.retries < 5 { host: host }
    on Disconnect: Connected => Disconnected { retries: 0 }
    on HeartbeatTimeout: Connected => Disconnected { retries: 0 }
 
    default { state }
}
 
fn main() {
    var conn = Connection.Idle;
    _ = conn.step(Connection.Event.Connect { host: "10.0.0.1" });
    println(conn.state_name()); // Connecting
}

Failure-aware Actor Calls

This local-actor example shows the failure-aware call boundary: every request returns a typed result that the caller handles or propagates.

actor_calls.hew
// Actor calls make failure part of the method surface. Every call yields a
// Result, so callers handle a stopped actor or expired request deadline alongside
// the successful value. The error branch below has the typed
// ActorError<Never, Never> carried by this method.
actor Worker {
    let id: i64;
    receive fn process(task: i64) -> i64 {
        task + id
    }
}
 
fn dispatch(task: i64) -> i64 fails ActorError<Never, Never> {
    let w = spawn Worker { id: 1 };
    // `?` on the binding unwraps Ok and propagates a typed ActorError<Never, Never>
    // on the failure branch.
    let answer = (w.process(task))?;
    answer
}
 
fn main() {
    match dispatch(7) {
        .Ok(v) => println(f"result = {v}"),
        .Err(_) => println("dispatch failed"),
    }
}

Wire Contracts

The profile schema gives every field a stable numeric tag. The optional verified field can be omitted by older or newer peers while the compatibility checker tracks tag changes.

user.hew
// Wire types carry explicit field tags (@1, @2, ...) for forward- and
// backward-compatible binary encoding. Optional fields may be omitted by
// older or newer peers without breaking the schema.
#[wire]
type UserProfile {
    name: string @1;
    email: string @2;
    age: i32 @3;
    verified: Option<bool> @4 optional;
}

Type System

Hew's type system is built around actor isolation and capability-checked boundaries. The compiler enforces Send/Frozen checks, exhaustiveness, and actor isolation at compile time.

Generics with monomorphization

The compiler specializes generic types and functions for each concrete type used by the program.

Send + Frozen capability traits

Send marks values that can cross an actor boundary; Frozen marks deeply immutable values that can be shared.

Bidirectional type inference

Expected types flow into lambdas and generic calls, reducing annotations while leaving the inferred type visible to the checker.

Pattern matching with exhaustiveness

Match expressions are checked for completeness at compile time. The compiler ensures every possible case is handled.

C FFI with extern blocks

Extern blocks describe C symbols and their Hew signatures. FFI is unsafe by default, so ABI, pointer, and lifetime correctness remain the caller's responsibility.

Option<T> and Result<T, E>

Absence and recoverable failure are represented as explicit values, handled with pattern matching or propagated with ?.

Iterator trait with adapters

Iterator adapters such as map, filter, and fold compose lazy transformations over sequences.

Compilation

Hew compiles to native machine code today. The hew build command drives the Rust frontend and Rust LLVM backend, with each stage preserving more checker guarantees.

.hew → Frontend → Type Check → MIR → LLVM IR → Native Binary