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.
| Hew | Go | Rust | |
|---|---|---|---|
| Lines of code | 54 | 87 | 93 |
| 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 struct 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 | sync, protobuf | serde, 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.
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.
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.
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.