Pony — Reference
Source: https://www.ponylang.io/
Pony
- Created: 2014 by Sylvan Clebsch (PhD work at Imperial College London); first public release 2015
- Latest stable: ponyc 0.63.4 (2026-05-02)
- Status: Pre-1.0. Quote from ponylang.io: “Pony hasn’t reached version 1.0. Breaking changes still happen. The pool of ready-to-use libraries is small.” Stewarded by the Pony Foundation (501(c)(3)) since 2017
- Paradigms: Object-oriented + actor model + capabilities-secure
- Typing: Static, strong, structural (interfaces match by shape) + nominal (traits), generics, algebraic data types, reference capabilities (the headline feature)
- Memory: Per-actor garbage collection (no stop-the-world); inter-actor garbage tracking via ORCA (Ownership and Reference Counting for Actors); zero-copy message passing for
isotypes - Compilation: AOT via LLVM (own front-end
ponyc); native code only; no VM. Targets x86_64 / arm64 on Linux / macOS / Windows / FreeBSD; new Ubuntu 26.04 builds added in 0.63.4 - Primary domains: High-throughput concurrent servers, low-latency systems (finance, telco), exploratory data systems, anywhere data races would be expensive
- Official docs: https://www.ponylang.io/ · Tutorial: https://tutorial.ponylang.io/ · Stdlib: https://stdlib.ponylang.io/
At a glance
Pony’s killer feature: the type system mathematically proves data-race freedom at compile time, without locks, atomics, or borrow checking. It does this via reference capabilities — six modifiers (iso, val, ref, box, tag, trn) that annotate every reference and govern who can read/write/share. Combine that with a true actor model (lightweight asynchronous actors with per-actor heaps) and you get a language where you can write a million-actor server and the compiler guarantees no races, no deadlocks (no locks exist), and no nulls. Pony came out of Sylvan Clebsch’s PhD thesis at Imperial College, was acquired by Causality (he later joined Microsoft Research), and is now stewarded by the community Pony Foundation. Pre-1.0 with small ecosystem but a deeply serious language.
Getting started
Install via ponyup (the official toolchain manager):
# Linux/macOS
sh -c "$(curl --proto '=https' --tlsv1.2 -fsSL https://github.com/ponylang/ponyup/releases/latest/download/ponyup-init.sh)"
ponyup update ponyc release # latest stable (0.63.4)
ponyup default ponyc-release-0.63.4
ponyc --versionWindows: download MSI from https://github.com/ponylang/ponyc/releases or use ponyup via WSL.
Hello world (hello/main.pony):
actor Main
new create(env: Env) =>
env.out.print("Hello, world!")Build + run:
ponyc hello/ # produces ./hello/hello binary
./hello/helloProject layout: Directory = package. Each .pony file in a dir contributes to that dir’s package. The “main” package must define an actor Main with new create(env: Env).
myapp/
├── main.pony # actor Main
├── widget/
│ └── widget.pony # package widget
└── corral.json # if using corral for deps
Build/package tool:
ponycis the compiler.ponyc <dir>compiles.ponyc --debug,--release(default),--paths /extra/lib,--bin-name name,--output-bin <dir>. The Pony build is whole-program and slow (no incremental linking).corralis the package manager (separate tool, install viaponyup update corral release).corral init,corral add github.com/ponylang/http_server.git --version 0.7.0,corral fetch,corral run -- ponyc. Dependencies live in_corral/.ponytestis the built-in test framework — write a_test.ponyfile, define test classes implementingUnitTest, and run as a normal Pony program.
REPL: None. Pony has no REPL; the compile/run cycle is the workflow.
Basics
Types & literals:
- Numeric:
U8 U16 U32 U64 U128 USize ULong,I8…I128 ISize ILong,F32 F64. No implicit numeric conversions —let x: U64 = 1works (literal coerces) but(a: U32) + (b: U64)does not Bool(true/false),String(UTF-8 by convention but byte-addressable),None(the unit-like type — used where other languages usenull/unit)- Collections in stdlib:
Array[T],List[T],Map[K, V],Set[T] - Tuples:
(U64, String). Named via(x: U64, name: String). Created with(1, "hi"). Destructured withlet (a, b) = pair
Variables & scoping: let immutable, var mutable, embed for embedded fields (no indirection):
let x: U64 = 42 // immutable
var y: U64 = 0 // mutable
y = y + 1All variables must be definitively assigned before use (compiler-enforced). Type annotations required where inference can’t determine; many local lets infer.
Control flow: Expression-oriented. Each branch must produce the same type (or None):
let s = if x > 0 then "positive" else "non-positive" end
match value
| 0 => "zero"
| let n: U64 if n > 100 => "big"
| let n: U64 => "small"
end
while iter.has_next() do iter.next() end
for item in collection.values() do ... end
repeat ... until cond endPattern matching with match is exhaustive across union types.
Functions (methods): Defined inside classes/actors/primitives:
fun— synchronous method (callable on object)be— behavior, an asynchronous actor method (returns immediately, message-queued)new— constructorfun ref,fun box,fun iso— capability ofthisfor the call
class Counter
var n: U64 = 0
fun ref increment() => n = n + 1
fun current(): U64 => nStrings: String is mutable-by-default (capability-controlled). String iso for transferable, String val for shared-immutable. Concatenation: s1 + s2 (allocates). s.size() is bytes, not codepoints. Iterate codepoints via s.runes().
Collections: Array[T] (contiguous), List[T] (linked), Map[K, V] (hash), Set[T], RingBuffer[T], Heap[T]. All in collections package: use "collections".
Intermediate
Type system depth:
- Classes (
class Foo) — heap-allocated objects with mutable state, accessed via reference capabilities - Actors (
actor Foo) — like classes but with their own heap, mailbox, and behaviors. Only one behavior runs at a time per actor — internally race-free - Primitives (
primitive Foo) — single, stateless instance (like a singleton/enum-tag); used for tagged unions and sentinels.primitive Noneis THE canonical “no value” - Traits (
trait Foo) — nominal interface (must beis-declared); compile-time dispatch - Interfaces (
interface Foo) — structural; types match by having matching method shapes (no declaration needed) - Generics:
class Stack[T],fun first[A: Comparable[A]](xs: Array[A]): A?. ConstraintsT: SomeTrait. Reified at compile time - Algebraic data types via union (
|) and intersection (&):(String | None)is “string or none”.(Comparable & Stringable)is “must be both”
Modules (packages): Directory = package. use "collections" imports a package. use "package_name" if windows for OS-conditional. use "lib:foo" declares a C library to link.
Error handling — partial functions: Functions/expressions that may fail are marked with ?:
fun divide(a: U64, b: U64): U64 ? =>
if b == 0 then error end
a / bCalling: try divide(10, 2) else 0 end. The error keyword raises; try ... else ... end catches. Errors carry no value — they’re a single sentinel. To carry data, return a union like (U64 | DivError) and match.
Concurrency primitives — actors and behaviors:
- An
actorruns sequentially internally, but actors run in parallel - Behaviors (
be name(...)) are async; callingactor.behavior()enqueues a message and returns immediately - Only
tag,val, orisoreferences can be sent across actor boundaries (capability-enforced — anything else won’t compile) - No futures/promises in the language; the pattern is “send a behavior to me when done”
- The runtime scheduler is M:N — millions of actors run on a fixed pool of OS threads
I/O: All I/O is async via behaviors. Stdlib provides files, net, time (timers), process (subprocess management), signals. The HTTP server lives in the third-party http_server package. The Env object passed to Main.create carries out, err, input, and root capabilities for ambient authority.
Stdlib highlights: builtin (Array, String, primitives), collections (Map, Set, List), files, net, net/ssl, time, random, format, strings, regex, crypto, serialise (built-in serialization), term (TTY handling), bureaucracy (Registrar pattern), cli (arg parsing), ponytest, pony_test, process, signals, ponybench.
Advanced
Memory model — per-actor heap + ORCA:
- Each actor owns its own heap; objects are allocated in the actor that creates them
- Per-actor GC runs only when that actor is idle (between behavior invocations) — no stop-the-world, no global pause
- Cross-actor object lifetimes are tracked by ORCA (Ownership and Reference Counting for Actors): an asynchronous, distributed reference-counting protocol that handles cycles between actors via a separate cycle detector
- For sent objects:
isotransfers ownership (zero-copy; sender loses access)valshares immutably (zero-copy; refcounted by ORCA)tagshares an identity-only handle (no read access)
- The result: a language where async message passing has no data races AND no GC pause AND can be zero-copy
Concurrency deep dive:
- The runtime uses a work-stealing scheduler with one queue per OS thread
- Actor mailboxes are MPSC queues (lock-free)
- Backpressure: when an actor’s mailbox grows beyond a threshold, sending actors are scheduled less, throttling producers — built in
- Memory ordering on aarch64 was hardened in 0.63.4
- The runtime is the same as in Sylvan Clebsch’s thesis (“The Pony Programming Language”, 2017) — peer-reviewed correctness
FFI: use "lib:foo" declares a library to link. Then declare C functions with @:
use "lib:m"
primitive Math
fun sqrt(x: F64): F64 => @sqrt(x)Pointers via Pointer[U8], NullablePointer[T]. The @func[ReturnType](args) syntax explicitly calls a C function. Use compile_intrinsic for hot paths.
Reflection: Very limited — Pony favors compile-time everything. Stringable interface for runtime printing. serialise package allows binary serialization but requires opt-in via Serialisable capability. No runtime type tags beyond what unions provide via match.
Performance tools: ponyc --pic --debug for debuggable builds; --llvm-args=... to pass through to LLVM. Runtime stats: --ponyminthreads, --ponymaxthreads, --ponynoblock (disable blocking detection), --ponyversion. The ponybench package is a microbenchmark framework. External: perf, samply, Tracy via custom FFI bindings.
God mode
Reference capabilities — the heart of Pony:
| Cap | Meaning | Read? | Write? | Aliasable? | Sendable? |
|---|---|---|---|---|---|
iso | Isolated — sole reference | Yes | Yes | NO (1 ref only) | YES (transfers ownership) |
trn | Transition — write here, read-only elsewhere | Yes | Yes | Read-only aliases | NO (convert to val first) |
ref | Reference — normal mutable, single-actor | Yes | Yes | Yes (within actor) | NO |
val | Value — globally immutable | Yes | NO | Yes (anywhere) | YES (refcounted by ORCA) |
box | Read-only view (matches val OR ref) | Yes | NO | Yes (within actor) | NO |
tag | Identity only | NO | NO | Yes (anywhere) | YES (zero-copy) |
Every type/parameter/field/return/local has a capability. String alone is shorthand for String ref. Capabilities are checked at compile time; passing a ref where val is expected = error.
recover blocks for capability promotion: Inside a recover block, you build up a ref value that the compiler can prove is uniquely owned at block exit, then promotes it to iso (or val):
let s: String iso = recover String.create(20) .> append("Hello") endInside the recover, String.create returns ref, but because the compiler proves no external alias escapes, the result is iso-promoted. This is how you build complex isolated structures from non-isolated APIs.
Behaviors vs functions:
fun— synchronous; returns a valuebe— asynchronous behavior; ALWAYS returns immediately; result type is implicitNone. Only callable on actors. Sender enqueues; receiver runs later- Inside a behavior, calls to your own
funare synchronous; calls to another actor’sbeenqueue - Behaviors must take only sendable parameters (
iso,val,tag)
Actor model + cap system = compile-time data-race freedom:
The proof: an actor can only access objects via its references. To send an object to another actor, you must give up your reference (iso) or send something globally immutable (val) or only an identity (tag). The compiler enforces these rules. Therefore: no two actors ever simultaneously have write access to the same object. Therefore: no data races. This is checked at compile time, no runtime cost.
ORCA distributed GC:
The reference-counting protocol that tracks val/iso lifetimes between actors. An actor sending a val increments the destination’s local refcount-token; sending back the count adjustment is folded into normal message traffic. Cycles between actors are detected by a dedicated cycle detector that observes mailbox states. Result: cross-actor garbage is collected without any synchronous coordination.
FFI surface:
use "lib:ssl"
use @SSL_new[Pointer[_SSL] tag](ctx: Pointer[_SSLContext] tag)The use @name form declares a C function with a Pony-typed signature; the @name(args) calls it. Use compile_error "msg" to abort compilation conditionally.
Academic backstory: Sylvan Clebsch’s PhD thesis “The Pony Programming Language” (2017, Imperial College) is the formal reference. Co-authored papers with Sophia Drossopoulou on the type system soundness (“Deny capabilities for safe, fast actors”). Real proofs, not hand-waving — Pony is one of the few languages whose memory/type model has been formally verified for data-race freedom.
Structural typing — interfaces match by shape:
interface Stringable
fun string(): String
class Foo
fun string(): String => "foo"
let things: Array[Stringable] = [Foo] // Foo automatically matchesNo implements Stringable required — the compiler checks shape. Traits (trait) are nominal alternatives when you want explicit declarations.
Idioms & style
- Naming:
lower_snake_casefor fields/locals/methods/packages,UpperCamelCasefor classes/actors/primitives/traits/interfaces,_leading_underscoreisprivate(package-local) - Capability defaults: types default to
refwhen unspecified (mutable, actor-local). Annotate when you want to share or transfer - Formatter/linter: No official formatter (this is a noted gap). Community uses tab indentation per the style guide. Linter:
ponyc --pass=ir --output=/dev/nullfor full type-check;ponyc --verifyfor additional checks - Idiomatic patterns:
- Return
isofrom constructors when the caller will send the object across actors - Use
valfor shared-immutable data (config, lookup tables) - Use
tagactor refs as IDs to communicate “send to this actor without reading its state” - Build complex objects inside
recoverblocks to get anisoout - Prefer
primitive Noneover null/option types - Use partial functions (
?) for “this should never fail given preconditions”; use union types for “this regularly fails with a reason”
- Return
- Expert review focus: correct caps on every public method (especially
fun refvsfun box), no needlessrecover(compiler will reject if it can’t prove uniqueness, but unnecessary recoveries clutter code), correct sendability of behavior args, proper?propagation, no actor leaks (an actor with no references is GC’d; one held only bytagkeeps running)
Ecosystem
Small but high-quality. Most production Pony code is private/internal.
| Domain | Library |
|---|---|
| HTTP server | http_server, http_client (ponylang official) |
| TLS | net/ssl (built-in via OpenSSL FFI) |
| JSON | json (built-in package) |
| Logging | logger (ponylang official) |
| CLI | cli (built-in, getopt-style) |
| Config | ini (built-in for INI files) |
| Test | ponytest / pony_test (built-in framework) |
| Bench | ponybench (built-in microbenchmarks) |
| Build | ponyc + corral (deps) + ponyup (toolchain) |
| LSP | pony-lsp (in active development; new features in 0.63.4 — type hierarchy, signature help, inlay hints) |
| Editor | VS Code extension, Sublime, Vim — built on pony-lsp |
Notable users: Causality (Sylvan Clebsch’s company, acquired the IP), WallarooLabs (Wallaroo stream processor, large open-source Pony codebase, now archived but historically the canonical large Pony app), Microsoft (Sylvan worked on Verona partially inspired by Pony), various financial firms running internal services.
Gotchas
- Pre-1.0: Breaking changes between minor versions. Pin a
ponycversion withponyupand acorral.jsonlockfile - The cap system has a steep learning curve — expect days/weeks to internalize
iso/val/ref/box/tag/trn. The compiler error messages have improved but can still cite “incompatible capabilities” without explaining why intuitively recoverblocks are restrictive — only certain operations are allowed inside (no calls to outside-scoperefmethods that take/return non-sendable). Refactor when stuck- No null, but no exceptions either — partial functions (
?) carry NO error data. To carry an error reason, use a union type(Result | Error)andmatch - Compile times are slow — whole-program LLVM compilation. Incremental compilation does not exist
- No REPL, no scripting mode — the compile/run cycle is the workflow
- No formatter — community gap. Style guide is hand-enforced via review
- Actor leaks are subtle — an actor only held by
tagreferences stays alive. To stop an actor, you typically have it process a “shutdown” message and stop scheduling new work String.size()is bytes, not codepoints — same gotcha as Rust/Go- Numeric coercion is strict — explicit
.u64()conversions everywhere; compiler errors on mixed-width arithmetic embedvs reference fields:embedlays the field out inline (no indirection, but requires the type be sized). Subtle perf and lifetime implications- Behaviors can’t return values — pattern is “send a callback actor reference and have me invoke a behavior on you when done”
- Backpressure can starve actors — if an actor’s mailbox grows huge, the scheduler de-prioritizes its senders. Good in steady state, can cause unexpected latency in bursts
- Distributed Pony is research, not production — there’s no built-in cross-machine actor protocol; multi-machine deployments roll their own (often via stdlib
net)
Citations
- Pony official site: https://www.ponylang.io/
- Tutorial: https://tutorial.ponylang.io/
- Reference capabilities chapter: https://tutorial.ponylang.io/reference-capabilities/reference-capabilities.html
- Standard library docs: https://stdlib.ponylang.io/
- ponyc 0.63.4 release (2026-05-02): https://github.com/ponylang/ponyc/releases/tag/0.63.4
- Source: https://github.com/ponylang/ponyc
- ponyup (toolchain manager): https://github.com/ponylang/ponyup
- corral (package manager): https://github.com/ponylang/corral
- Sylvan Clebsch’s thesis “The Pony Programming Language” (2017): https://www.ponylang.io/media/papers/a_string_of_ponies.pdf
- “Deny Capabilities for Safe, Fast Actors” (OOPSLA 2015): https://www.ponylang.io/media/papers/fast-cheap.pdf
- ORCA paper “Ownership and Reference Counting based Garbage Collection in the Actor World”: https://www.ponylang.io/media/papers/OPSLA237-clebsch.pdf
- Pony Foundation: https://www.ponylang.io/community/pony-foundation/