Fan-out/Fan-in
Distribute work to N workers and collect all results in one place. A scatter-gather pattern used in parallel processing, map-reduce, and load distribution.
Hew
Workers hold a typed ActorType handle to the collector actor and call collector.report(). The actor reference carries the message route and the method call performs the send.
actor Collector {
var total: i64 = 0;
receive fn report(value: i64) {
total += value;
}
receive fn get_total() -> i64 {
total
}
}
actor Worker {
let collector: Collector;
receive fn compute(n: i64) {
collector.report(n * n).expect("actor stopped");
}
}
fn main() {
let collector = spawn Collector { total: 0 };
let w1 = spawn Worker { collector: collector };
let w2 = spawn Worker { collector: collector };
scope {
let first = fork w1.compute(3);
let second = fork w2.compute(4);
(await first).expect("actor stopped");
(await second).expect("actor stopped");
};
match collector.get_total() {
.Ok(total) => println(f"total: {total}"),
.Err(_) => println("ask failed"),
}
}Go
Go uses two channels (jobs and results) with goroutines. The channel version needs an explicit lifecycle contract: close the jobs channel, drain the expected results, and add cancellation for early exits.
func worker(id int, jobs <-chan int, results chan<- int) {
for n := range jobs {
results <- n * n
}
}
func main() {
jobs := make(chan int, 10)
results := make(chan int, 10)
// Start workers
for i := 0; i < 2; i++ {
go worker(i, jobs, results)
}
// Send work
jobs <- 3
jobs <- 4
close(jobs) // Forget this = deadlock
// Collect results
total := 0
for i := 0; i < 2; i++ {
total += <-results // Wrong count = goroutine leak
}
fmt.Printf("total: %d\n", total)
}What this shows
Developer experience
Hew's actor-reference pattern eliminates channel lifecycle management. Workers don't know about channels — they call methods on their collector. The topology is expressed through actor fields, not channel wiring.
Debugging
Go channel pipelines can hang when close/drain/cancel paths are inconsistent. Hew's local actor version moves the main risks to message ordering, timeout choices, and actor lifecycle boundaries.
Trade-offs
Go's channel pattern supports dynamic topologies where the number of workers changes at runtime: channels can be passed around, combined, and multiplexed. Hew's example fixes the topology at spawn time and replaces channel close/drain coordination with actor lifecycle and request-error handling.