Axon for Go Developers
If you are coming from Go, you already appreciate most of what Axon is trying to do: simple syntax, fast compilation, native binaries, and serious concurrency primitives. The differences are mostly about how those goals are achieved.
What’s identical (almost)
Section titled “What’s identical (almost)”Both Go and Axon produce small, statically-linked native binaries from a simple source layout. Both compile fast (Go: a few seconds; Axon: similar). Both have built-in testing tools. Both prioritize readability over expressiveness.
| Go | Axon |
|---|---|
package main | (module ...) |
import "fmt" | (import fmt "std/fmt.axs") |
func main() { ... } | (fn main () i32 ...) |
func Add(a, b int) int | (fn add ((a i64) (b i64)) i64 ...) |
type Point struct { X, Y int } | (struct Point ((x i64) (y i64))) |
var x int = 5 | (let (x i64) (i64 5)) |
for i := 0; i < n; i++ {} | (for ((i i64)) (range (i64 0) n (i64 1)) ...) |
go func() { ... } | (go (fn () ...)) |
ch := make(chan int) | (let (ch (call chan_new (i64 16)))) |
defer f.Close() | not directly supported; use explicit cleanup on every return path |
if err != nil { ... } | (if (is_err err) (block ...)) — errors are explicit return values |
What’s different
Section titled “What’s different”- Goroutines vs green threads. Go has a full M:N runtime scheduler that multiplexes goroutines onto OS threads. Axon’s
(go ...)is cooperative — the scheduled function runs until it explicitly yields viaawait,chan_recv, orevloop_wait. This makes Axon’s runtime tiny (no scheduler overhead) but requires that hot loops yield periodically. - Channels work the same way. Both Go and Axon provide unbuffered/buffered channels, send/recv, select. The Axon API is lower-level (no
selectkeyword yet) but the semantics are identical. - Errors are values, both languages. Go has
(T, error). Axon has(Result T E)fromstd/result. Same idea. - No garbage collector in Axon. Go has a concurrent GC (≈1ms pauses). Axon requires explicit
(alloc T)/(free ptr). This trades Go’s runtime cost for Axon’s zero-pause determinism. - No interface satisfaction in Axon. Go uses structural typing (
type Reader interface { Read(p []byte) (n int, err error) }). Axon uses nominal typing with traits ((trait Readable ((self T) (read (ptr u8) i64) i64))). - No build tags or conditional compilation. Axon has one binary per target triple; platform-specific code uses traits and the dispatcher.
Code Comparison
Section titled “Code Comparison”A simple web handler:
Go:
package main
import ( "encoding/json" "net/http")
type Greeting struct { Message string `json:"message"`}
func main() { http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(Greeting{Message: "Hello, World!"}) }) http.ListenAndServe(":8080", nil)}Axon:
(import std/http)(import std/json)
(struct Greeting ((message string)))
(fn main () i32 (call http.serve (i64 8080) (fn ((req (ptr http.Request)) (resp (ptr http.Response))) (set resp.body (call json.encode (Greeting (str "Hello, World!")))) (i32 0)))) (i32 0))The Axon version is longer because every callback, allocation, and error path is explicit. The runtime is identical (kernel-level epoll loop), the binary is ~30% smaller, and there is no GC pause.
Concurrency patterns
Section titled “Concurrency patterns”| Pattern | Go | Axon |
|---|---|---|
| Spawn a task | go func() {...}() | (go (fn () ...)) |
| Wait for completion | wg.Wait() | (join handle) |
| Channel send | ch <- val | (call chan_send ch val) |
| Channel recv | val := <-ch | (let (val (call chan_recv ch)) ...) |
| Non-blocking try | select { case ch <- v: default: ... } | (if (eq (call chan_try_send ch v) (i64 0)) ...) |
| Mutex | var mu sync.Mutex; mu.Lock(); defer mu.Unlock() | (let (m (call mutex_new))) (call mutex_lock m) ; ... (call mutex_unlock m) |
| Cancel | ctx, cancel := context.WithCancel(...) | not built-in; use actor message + poison pill |
| Worker pool | for i := 0; i < N; i++ { go worker(ch) } | spawn N, join all |
When to stay in Go vs reach for Axon
Section titled “When to stay in Go vs reach for Axon”Stay in Go for:
- Microservices that fit in the standard library’s “batteries included” model
- Codebases that depend heavily on the Go ecosystem (gRPC, kubernetes, prometheus clients)
- Teams that prefer goroutines + channels and want runtime scheduling for free
Reach for Axon for:
- Sub-millisecond-latency services (HFT, gaming servers, embedded)
- Performance-critical paths where Go’s GC pauses are unacceptable
- Code that needs to compile to WebAssembly for browser deployment
- AI-generated services where the simpler S-expression syntax reduces hallucinations
Summary for Gophers
Section titled “Summary for Gophers”- Syntax: S-expressions instead of C-like syntax with mandatory braces.
- Concurrency: Cooperative green threads (you yield) instead of preemptive goroutines (runtime decides).
- Memory: Explicit
(alloc)/(free)instead of concurrent GC. - Errors:
(Result T E)value type instead of(T, error)tuples. - Standard library: Smaller than Go’s stdlib but covers the same primitives (HTTP, channels, mutexes, time, JSON, hashmap).