How Hew Compares

The same counter service implemented in Hew, Go, and Rust. Hew uses direct method calls on actors — no channels, no .send(), no mpsc.

HewGoRust
Lines of code548793
State isolationLanguage-level actors, direct method callsManual (sync.Mutex)Manual (Arc<Mutex<>>)
Data race preventionCompile-timeRuntime detectorCompile-time
Fault recoverysupervisor keywordManual goroutine restartManual (or library)
Network typeswire struct with HBF encodingProtobuf + codegenserde + derive macros
Memory modelRAII + per-actor heaps, no GCGC, goroutine schedulerRAII + ownership, no GC
External deps neededNonesync, protobufserde, bincode, tokio

Hew

Actors, supervision, and wire types are language constructs. The Counter actor owns its state — no locks needed. Callers use direct method calls like counter.increment(10) — cleaner than Go channels or Rust mpsc.

counter_service.hew
actor Counter {    count: i32,    receive fn increment(amount: i32) -> i32 {        self.count = self.count + amount;        self.count    }    receive fn get_count() -> i32 {        self.count    }}supervisor CounterPool {    strategy: one_for_one,    max_restarts: 5,    window: 60,    children: [Counter]}wire struct CounterUpdate {    counter_id: u32 @1,    new_count: i32 @2,    timestamp: u64 @3}fn main() -> i32 {    let counter = spawn Counter { count: 0 };    counter.increment(10);                  // direct method call    let count = await counter.get_count();  // request-response    println(count);    0}

Go

Go uses goroutines and channels for concurrency, but state isolation requires manual mutex management. There is no built-in supervision — restart logic must be implemented by the developer. Network message types require an external tool like Protocol Buffers.

counter_service.go
package main

import (
    "fmt"
    "sync"
)

type Counter struct {
    mu    sync.Mutex
    count int32
}

func NewCounter() *Counter {
    return &Counter{}
}

func (c *Counter) Increment(amount int32) int32 {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.count += amount
    return c.count
}

func (c *Counter) GetCount() int32 {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.count
}

// No built-in supervision. Manual restart logic.
type CounterPool struct {
    counters     []*Counter
    maxRestarts  int
    restartCount int
}

func NewCounterPool(size int) *CounterPool {
    pool := &CounterPool{
        counters:    make([]*Counter, size),
        maxRestarts: 5,
    }
    for i := range pool.counters {
        pool.counters[i] = NewCounter()
    }
    return pool
}

func (p *CounterPool) RestartChild(idx int) {
    p.restartCount++
    if p.restartCount > p.maxRestarts {
        fmt.Println("Exceeded restart limit")
        return
    }
    p.counters[idx] = NewCounter()
}

// No wire types. Plain struct with JSON tags.
type CounterUpdate struct {
    CounterID uint32 `json:"counter_id"`
    NewCount  int32  `json:"new_count"`
    Timestamp uint64 `json:"timestamp"`
}

func fibonacci(n int32) int32 {
    if n <= 1 {
        return n
    }
    return fibonacci(n-1) + fibonacci(n-2)
}

func main() {
    result := fibonacci(10)
    fmt.Printf("fibonacci(10) = %d\n", result)
}

Rust

Rust prevents data races at compile time through its ownership system, but sharing state across threads requires Arc<Mutex<>> wrappers. Hew takes a different approach: no intra-actor borrow checker — actors are single-threaded, so Rust-style borrow checking is unnecessary within actors. Safety is enforced only at actor boundaries via Send and move semantics. Like Go, Rust has no built-in supervision or wire types — these require external crates.

counter_service.rs
use std::sync::{Arc, Mutex};

struct Counter {
    count: i32,
}

impl Counter {
    fn new() -> Self {
        Counter { count: 0 }
    }

    fn increment(&mut self, amount: i32) -> i32 {
        self.count += amount;
        self.count
    }

    fn get_count(&self) -> i32 {
        self.count
    }
}

type SharedCounter = Arc<Mutex<Counter>>;

fn new_shared_counter() -> SharedCounter {
    Arc::new(Mutex::new(Counter::new()))
}

fn shared_increment(counter: &SharedCounter, amount: i32) -> i32 {
    let mut c = counter.lock().unwrap();
    c.increment(amount)
}

// No built-in supervision. Manual restart logic.
struct CounterPool {
    counters: Vec<SharedCounter>,
    max_restarts: i32,
    restart_count: i32,
}

impl CounterPool {
    fn new(size: usize) -> Self {
        let counters = (0..size).map(|_| new_shared_counter()).collect();
        CounterPool {
            counters,
            max_restarts: 5,
            restart_count: 0,
        }
    }

    fn restart_child(&mut self, idx: usize) {
        self.restart_count += 1;
        if self.restart_count > self.max_restarts {
            eprintln!("Exceeded restart limit");
            return;
        }
        self.counters[idx] = new_shared_counter();
    }
}

// No wire types. Needs serde + a serialization crate.
// #[derive(Serialize, Deserialize)]
struct CounterUpdate {
    counter_id: u32,
    new_count: i32,
    timestamp: u64,
}

fn fibonacci(n: i32) -> i32 {
    if n <= 1 { n }
    else { fibonacci(n - 1) + fibonacci(n - 2) }
}

fn main() {
    let result = fibonacci(10);
    println!("fibonacci(10) = {}", result);
}

What this shows

State isolation & messaging

In Hew, actor is a language construct. Callers use direct method calls — counter.increment(10) — not channels or .send(). This is cleaner than Go's ch <- msg channel pattern or Rust's tx.send(msg) mpsc boilerplate. Lambda actors use the <- operator. In Go and Rust, the developer manages concurrency primitives manually — mutexes, channels, or Arc wrappers.

Fault recovery

Hew's supervisor declaration specifies restart strategies in three lines. Go and Rust require the developer to write goroutine or thread monitoring and restart logic by hand, or adopt third-party libraries.

Network types

Hew's wire struct defines network message formats with tagged fields and Hew Binary Format (HBF) encoding directly in the language. HBF provides compact TLV encoding with forward/backward compatibility. Go and Rust typically use Protocol Buffers or serde, which add external toolchains and code generation steps.

Trade-offs

Go has a mature ecosystem, excellent tooling, and fast compile times. Rust has zero-cost abstractions and a proven ownership model. Hew uses RAII with per-actor heaps and deterministic destruction — no garbage collector, no GC pauses. It aims to reduce boilerplate for service-oriented systems by making actors, supervision, and wire types part of the language itself — at the cost of being a younger language with a smaller ecosystem.