How Hew Compares
The same counter service implemented in Hew, Go, and Rust. Hew uses direct method calls on actors — cleaner syntax, fewer primitives.
Pattern deep-dives
Each page compares a specific concurrency or systems pattern across languages with full code examples and analysis.
Select & Join
Race async operations or wait for all of them. Hew vs Go vs Rust.
Fan-out/Fan-in
Distribute work to N workers, collect results. Hew vs Go.
Backpressure
Bounded mailboxes and overflow strategies. Hew vs Go.
Supervision
Crash detection, restart budgets, and strategies. Hew vs Erlang vs Go.
Wire Types
Evolvable network message formats. Hew vs Go+Protobuf vs Rust+serde.
Actor Pipeline
Multi-stage processing chains with typed actor references. Hew vs Go.
| Hew | Go | Rust | |
|---|---|---|---|
| Lines of code | 32 | 68 | 72 |
| State isolation | Language-level actors, direct method calls | Manual (sync.Mutex) | Manual (Arc<Mutex<>>) |
| Data race prevention | Compile-time | Runtime detector | Compile-time |
| Fault recovery | supervisor keyword | Manual goroutine restart | Manual (or library) |
| Network types | wire type with HBF encoding | Protobuf + codegen | serde + derive macros |
| Memory model | RAII + per-actor heaps, no GC | GC, goroutine scheduler | RAII + ownership, no GC |
| External deps needed | None | protobuf | serde, tokio |
Hew
Actors, supervision, and wire types are language constructs. The Counter actor owns its state exclusively. Callers use direct method calls like counter.increment(10) — the actor handles isolation, so the call site stays clean.
actor Counter {
let count: i32;
receive fn increment(amount: i32) -> i32 {
count = count + amount;
count
}
receive fn get_count() -> i32 {
count
}
}
supervisor CounterPool {
child counter: Counter
restart(permanent)
budget(5, 60s)
strategy(one_for_one);
}
wire type CounterUpdate {
counter_id: u32 @1;
new_count: i32 @2;
timestamp: u64 @3;
}
fn main() {
let counter = spawn Counter(count: 0);
counter.increment(10);
let count = await counter.get_count();
println(count);
}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.
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 main() {
counter := NewCounter()
counter.Increment(10)
fmt.Printf("count: %d\n", counter.GetCount())
}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.
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: u32,
restart_count: u32,
}
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 main() {
let counter = new_shared_counter();
let count = shared_increment(&counter, 10);
println!("count: {}", count);
}What this shows
State isolation & messaging
In Hew, actor is a language construct. Callers use direct method calls — counter.increment(10) — and the actor handles isolation internally. Lambda actors use the <- send operator: worker <- 42. The call site reads like ordinary function calls because the concurrency model is part of the language. Go and Rust require the developer to manage concurrency primitives — 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 type 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 — predictable latency with zero GC overhead. 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.