Skip to content

Program Annotations

In Pony, we provide a special syntax for implementation-specific annotations to various elements of a program. The basic syntax is a comma-separated list of identifiers surrounded by backslashes:

\annotation1, annotation2\

Here, annotation1 and annotation2 can be any valid Pony identifier, i.e. a sequence of alphanumeric characters starting with a letter or an underscore.

What can be annotated

Annotations are allowed after any scoping keyword or symbol. The full list is:

  • actor
  • class
  • struct
  • primitive
  • trait
  • interface
  • new
  • fun
  • be
  • if (only as a condition, not as a guard)
  • ifdef
  • elseif
  • else
  • while
  • repeat
  • until
  • for
  • match
  • | (only as a case in a match expression)
  • recover
  • object
  • { (only as a lambda)
  • with
  • try
  • then (only when part of a try block)

The effect of annotations

Annotations are entirely implementation-specific. In other words, the Pony compiler (or any other tool that processes Pony programs) is free to take any action for any annotation that it encounters, including not doing anything at all. Annotations starting with ponyint are reserved by the compiler for internal use and shouldn’t be used by external tools.

Annotations in the Pony compiler

The following annotations are recognised by the Pony compiler. Note that the Pony compiler will ignore annotations that it doesn’t recognise, as well as the annotations described here if they’re encountered in an unexpected place.

packed

Recognised on a struct declaration. Removes padding in the associated struct, making it ABI-compatible with a packed C structure with compatible members (declared with the __attribute__((packed)) extension or the #pragma pack preprocessor directive in many C compilers).

struct \packed\ MyPackedStruct
  var x: U8
  var y: U32

likely and unlikely

Recognised on a conditional expression (if, while, until and | (as a pattern matching case)). Gives optimisation hints to the compiler on the likelihood of a given conditional expression.

if \likely\ cond then
  foo
end
while \unlikely\ cond do
  bar
end
repeat
  baz
until \likely\ cond end
match obj
| \likely\ expr => foo
| \unlikely\ let capt: T => bar
end

nodoc

Recognised on objects and methods (actor, class, struct, primitive, trait, interface, new, be, fun). Indicates to the documentation system that the item and any of its children shouldn’t be included in generated output.

class \nodoc\Foo
   """
   We don't want this class and its methods to appear in generated documentation
   """

nosupertype

Recognised on objects(actor, class, primitive, struct). A type annotated with nosupertype will not be a subtype of any other type (except _), even if the type structurally provides an interface. If a nosupertype type has a provides list, a compiler error is reported. As a result, a nosupertype type is excluded from both nominal and structural subtyping.

Here’s an example of how nosupertype can be important:

class Empty

class Foo
  fun foo[A: Any](a: (A | Empty val)) =>
    match consume a
    | let a': A => None
    end

The above code won’t compile because you could supply Empty ref. Doing so results in a compiler error about an unsafe match because we would need to distinguish between Empty val and Empty ref at runtime.

By adding nosupertype to the definition of Empty, we declare that Empty is not a subtype of Any and thereby allow the code to compile as there is no longer an unsafe match.

class \nosupertype\ Empty

class Foo
  fun foo[A: Any](a: (A | Empty val)) =>
    match consume a
    | let a': A => None
    end

nosupertype is particularly valuable when constructing generic classes like collections that need a marker class to describe “lack of an item”.

exhaustive

Recognised on a match expression. Asserts that the match must explicitly handle all possible cases. Without this annotation, a non-exhaustive match silently gets an implicit else None added by the compiler, which widens the result type to include None and often produces a confusing indirect type error like “function body isn’t the result type”. With \exhaustive\, the compiler instead reports match marked \exhaustive\ is not exhaustive, making the problem immediately clear.

The annotation is also useful on matches that are already exhaustive as a future-proofing measure. Without it, if a new member is later added to the union type and the match isn’t updated, the compiler silently injects else None. You’ll only get an error if the match result is assigned to a variable whose type doesn’t include None — otherwise the bug is completely silent. With \exhaustive\, the compiler catches the missing case immediately.

fun name(colour: Colour): String =>
  match \exhaustive\ colour
  | Red => "red"
  | Green => "green"
  | Blue => "blue"
  end