| name | pony-ref |
| description | Load the Pony language reference (capabilities, PonyCheck, stdlib pitfalls, mort pattern). Load it before Pony coding sessions. |
| disable-model-invocation | false |
Pony Language Reference
Reference Capabilities Quick Reference
| Cap | Aliases | Read | Write | Share across actors | Use case |
|---|
iso | none | yes | yes | yes (by moving) | Isolated mutable data to send between actors |
trn | box | yes | yes | no | Mutable, will become immutable later |
ref | ref | yes | yes | no | Normal mutable data within an actor |
val | val | yes | no | yes | Immutable, shareable |
box | box/val | yes | no | no | Read-only view (accepts ref or val) |
tag | any | no | no | yes | Identity only, opaque reference |
Capability Subtyping
iso^ (ephemeral) can become anything
trn^ can become ref, val, or box
ref <: box <: tag
val <: box <: tag
iso <: tag (non-ephemeral iso can only become tag)
trn <: box (non-ephemeral trn can only become box)
Key Patterns
Consuming iso/trn: Use consume to move ownership and enable capability conversion:
var a: String iso = "hello".clone()
var b: String val = consume a // a is now unusable
Recover blocks: Lift capability of result when inner aliases are sendable:
let s: String iso = recover iso String.create() end
Automatic receiver recovery: Can call ref methods on iso/trn if all args are sendable:
let s: String iso = "hello".clone()
s.append(" world") // works because " world" is val (sendable)
Destructive read for fields: Can't consume fields, but can swap:
var old_value: Foo iso = _field = new_value // returns old value
Method Chaining (.>)
The .> operator calls a method but returns the receiver instead of the method's return value. This enables fluent chaining on types with mutating methods that return None (or any unwanted return):
// Without chaining — repeating the receiver on every line
let buf = Array[U8]
buf.append("HTTP/1.1")
buf.push(' ')
buf.append("200 OK")
buf.append("\r\n")
// With chaining — receiver flows through
let buf = Array[U8]
buf.>append("HTTP/1.1")
.>push(' ')
.>append("200 OK")
.>append("\r\n")
Works with constructors too — String.>append("hello").>append(" world") creates a String, appends to it, and the whole expression evaluates to the String.
When NOT to chain: Don't use .> when you need the method's actual return value. .> discards the return and gives you the receiver back instead.
Common Gotchas
-
iso to box/ref: Can't alias iso as readable without consuming. Use consume to get iso^ which can become anything.
-
String.from_iso_array returns iso: Must consume before using in operations expecting box:
var s = String.from_iso_array(consume data)
s.strip()
env.out.print("Result: " + consume s)
-
Stdin is async: Use InputNotify interface. Buffer input until newline for line-based input.
-
Actor constructors return tag: Actors are always tag capability from outside.
-
Default capabilities: Classes default to ref, primitives to val, actors to tag.
-
_ scoping depends on what it's on: _field (leading underscore on a field) is type-private — only accessible within the same type. _method (leading underscore on a method, constructor, or behavior) is package-private — accessible by any type in the same package but not outside it. _Type (leading underscore on a type name) is also package-private. This distinction matters: you CAN call SomeType._helper() from another type in the same package (including tests), but you CANNOT read SomeType._field from outside the type.
-
Type aliases support docstrings: A docstring string literal inside a type alias compiles and is included in generated documentation, just like classes and primitives.
-
consume requires a variable or field, not an expression: consume some_method() is a compile error. Assign the result to a let first, then consume (or just assign directly when the target capability allows it). For example, when a method returns String iso^, let s: String val = the_method()? works directly — ephemeral iso^ can be assigned to val without an explicit consume.
-
Don't use marker traits to group variants: When a trait carries no methods and exists only to group a closed set of types (e.g., trait val MyError with several primitives is MyError), use a union type instead (type MyError is (ErrA | ErrB | ErrC)). Marker traits are open — anyone can implement them — so the compiler can't enforce exhaustive matching. Union types are closed and enable match \exhaustive\, producing compile errors when a variant is unhandled. Reserve traits for when you actually need openness.
-
Actor constructors cannot fail: Actor constructor calls return immediately and always succeed from the caller's perspective — there is no way to signal a construction failure. If a partial function's error path would leave the actor in an invalid or unusable state, move the fallible work before actor creation: validate and prepare all inputs in the calling code first, then pass only known-good data to the constructor. This is the "supply chain" pattern — the actor's constructor receives pre-validated inputs, so it never needs to handle errors that would compromise its integrity.
-
Prefer embed over let for class/struct fields: When a field's type is a class or struct and it's initialized from a constructor expression, use embed instead of let. embed is the recommended default — it avoids a pointer indirection and a separate heap allocation. Only use let when the field might outlive its parent (exterior references would prevent GC of the parent object), or when the type is an interface, trait, primitive, or numeric type (which can't be embedded).
-
Any is a code smell: Using Any is almost never what you want. It erases type information and forces runtime checks where compile-time guarantees should exist. In most cases, the right answer is a generic type parameter — this preserves type safety and lets the compiler enforce constraints. If you're reaching for Any, stop and ask whether a generic would work instead.
-
Don't use fun tag on primitives: Primitives are val, and the default method receiver is box. Since val <: box, plain fun works on primitives without annotation. fun tag compiles but pointlessly weakens the receiver to tag, which can't read fields — it's never what you want on a primitive. Just use fun.
-
Constraints use the named type's default cap: When a type parameter constraint names a type without an explicit capability, the constraint gets the type's default cap — ref for classes and interfaces, val for primitives, tag for actors. Writing class Foo[A: Stringable] means A: Stringable ref, not A: Stringable #any. To constrain to any capability, write the cap set explicitly: class Foo[A: Stringable #any]. This matters most in intersection constraints where one member has an explicit cap — (Comparable[A] val & Stringable) constrains Stringable to ref, making the intersection unsatisfiable; write (Comparable[A] val & Stringable val).
-
A tag reference doesn't keep an object's fields alive: tag is opaque, so ORCA doesn't trace through it. Holding an object only through a tag keeps the object's own allocation alive, but its fields can still be collected. Through a val, ref, or box reference ORCA does trace the fields and keeps them alive. This matters across an FFI boundary: to keep a Pony object alive while C holds a raw pointer to it, root the object itself through a non-tag reference, or root the exact object C points at. Rooting a container through a tag and expecting its fields to survive doesn't work.
Integer Arithmetic Modes
Pony integers have three arithmetic modes — choose based on how you want to handle overflow:
| Mode | Operators/Methods | Overflow behavior | Use case |
|---|
| Wrapping (default) | +, -, *, /, % | Silent wrap-around | When overflow is impossible or harmless |
| Partial | +?, -?, *?, /?, %?, %%? | Errors (?) | When overflow means invalid input |
| Checked | addc(), subc(), mulc(), etc. | Returns (result, Bool) | When you need to branch on overflow |
Partial methods: add_partial(), sub_partial(), mul_partial(), div_partial(), rem_partial(), mod_partial(). Also fld_partial(), mod_partial() on signed types. Division by zero also errors.
Checked methods: addc(), subc(), mulc(), divc(), remc(). Also fldc(), modc() on signed types. The Bool in the return tuple is true when overflow/underflow (or division by zero) occurred.
Common pattern — accumulating digits with overflow detection:
// Errors if the accumulated value overflows I64
result = result.mul_partial(10)?.add_partial(digit.i64())?
Syntax Essentials
actor Main // Actor definition
let _env: Env // Immutable field (underscore = private)
var _count: U32 = 0 // Mutable field with default
new create(env: Env) => // Constructor
_env = env
be some_behavior(x: U32) => // Behavior (async message handler)
_count = _count + x
fun ref mutating_method(): U32 => // Method that can modify state
_count = _count + 1
_count
fun box readonly_method(): U32 => // Read-only method
_count
class MyClass
fun apply(): String => "called" // Makes instances callable: obj()
interface Printable // Structural typing
fun string(): String
trait Named // Nominal typing (must explicitly implement)
fun name(): String
primitive Utils // Singleton, default cap is val
fun helper(): U32 => 42
Generic Type Argument Inference
Type arguments can often be omitted when the compiler can infer them from the call's arguments:
// Before: Sorter.sort[U8](U8(3), U8(1))
// After:
let result = Sorter.sort(U8(3), U8(1))
// Constructors too:
// Before: Pair[String, U8]("hello", U8(42))
// After:
let p = Pair("hello", U8(42))
Inference works when each type parameter is determined by at least one argument position. Array literals and lambdas at positions whose parameter type mentions the type parameter are skipped during inference and typed afterward, so at least one other argument must pin the type.
Write type arguments explicitly when using: union-typed parameters, type aliases wrapping a generic type, where named-only arguments, or when no argument position determines the type parameter.
PonyCheck (Property-Based Testing)
Two ways to write property tests:
Property1[T] trait (standalone class, recommended for reusable properties) — implement name(), gen(), property(), register with test(Property1UnitTest[T](MyProperty))
PonyCheck.for_all (inline lambdas within a UnitTest) — convenient for quick one-offs, but generators must be val: recover val Generators.u8() end
Custom generator pattern: Custom generators MUST be anonymous objects, not named primitives or classes. The correct pattern is always:
fun gen(): Generator[String] =>
Generator[String](
object is GenObj[String]
fun generate(r: Randomness): String^ =>
"my value"
end)
Do NOT try primitive MyGen is GenObj[String] or class MyGen is GenObj[String] — this is a common agent mistake that produces confusing compiler errors ("can't find definition of 'T'"), which then gets misattributed to a compiler bug. It's a usage error. Return either a bare value or (value, shrink_iterator) tuple from generate().
Generator composition: .filter(), .map(), .flat_map(), .union(), plus Generators.zip2/3/4, Generators.map2/3/4, Generators.frequency (weighted selection).
Useful built-ins to remember:
IntProperty trait — tests a property across all 14 Pony integer types automatically
- ASCII range types (
ASCIIPrintable, ASCIILetters, ASCIIDigits, etc.) for controlling string generation character sets
Generators.one_of for selecting from a fixed set, Generators.frequency for weighted selection
Generators.set_of, Generators.map_of take (gen where min = 0, max = 100) — pass min = 1 for non-empty collections
Gotchas:
flat_map shrinking is incomplete (TODO in source) — only shrinks on inner generator, not outer
- Collection shrinking generates fresh random elements, just fewer — shrunken collections are NOT subsets of the original
- No built-in
F32/F64 generators — use Generators.repeatedly with a lambda as workaround
- Seed is printed in test output — pass back via
PropertyParams(where seed' = N) to reproduce a failure
value.clone() in for_all lambdas: Generated String values arrive as ref capability inside for_all lambdas. To use them inside recover val blocks (e.g., building a val array of tuples), call value.clone() first — clone() on a ref returns an iso^ which can be consumed into the recover block. Without this, the ref alias prevents the block from lifting to val.
Generators.array_of[T] produces ref arrays, not val: Generators.array_of[U8](Generators.u8()) yields Generator[Array[U8] ref], which can't be used in zip2/map2 when the target type needs Array[U8] val. Workaround: use Generators.map2 with a fill byte + length, constructing the val array inside the lambda: {(fill, len) => (fill, recover val Array[U8].init(fill, len) end)}.
PropertyParams defaults: 100 samples, 10 max shrink rounds, 5 max generator retries, 60s timeout, non-async. Override by implementing params() on your Property trait.
Stdlib Pitfalls and Patterns