A primitive is similar to a class, but there are two critical differences:
- A primitive has no fields.
- There is only one instance of a user-defined primitive.
Having no fields means primitives are never mutable. Having a single instance means that if your code calls a constructor on a primitive type, it always gets the same result back (except for built-in “machine word” primitives, covered below).
What can you use a primitive for?¶
There are three main uses of primitives (four, if you count built-in “machine word” primitives).
- As a “marker value”. For example, Pony often uses the primitive
Noneto indicate that something has “no value”. Of course, it does have a value, so that you can check what it is, and the value is the single instance of
- As an “enumeration” type. By having a union of primitive types, you can have a type-safe enumeration. We’ll cover union types later.
- As a “collection of functions”. Since primitives can have functions, you can group functions together in a primitive type. You can see this in the standard library, where path handling functions are grouped in the primitive
Path, for example.
// 2 "marker values" primitive OpenedDoor primitive ClosedDoor // An "enumeration" type type DoorState is (OpenedDoor | ClosedDoor) // A collection of functions primitive BasicMath fun add(a: U64, b: U64): U64 => a + b fun multiply(a: U64, b: U64): U64 => a * b actor Main new create(env: Env) => let doorState : DoorState = ClosedDoor let isDoorOpen : Bool = match doorState | OpenedDoor => true | ClosedDoor => false end env.out.print("Is door open? " + isDoorOpen.string()) env.out.print("2 + 3 = " + BasicMath.add(2,3).string())
Primitives are quite powerful, particularly as enumerations. Unlike enumerations in other languages, each “value” in the enumeration is a complete type, which makes attaching data and functionality to enumeration values easy.
Built-in primitive types¶
primitive keyword is also used to introduce certain built-in “machine word” types. Other than having a value associated with them, these work like user-defined primitives. These are:
Bool. This is a 1-bit value that is either
I128. Signed integers of various widths.
U128. Unsigned integers of various widths.
F64. Floating point numbers of various widths.
USize correspond to the bit width of the native type
size_t, which varies by platform.
ULong similarly correspond to the bit width of the native type
long, which also varies by platform. The bit width of a native
int is the same across all the platforms that Pony supports, and you can use
U32 for this.
Primitive initialisation and finalisation¶
Primitives can have two special functions,
_init is called before any actor starts.
_final is called after all actors have terminated. The two functions take no parameter. The
_final functions for different primitives always run sequentially.
A common use case for this is initialising and cleaning up C libraries without risking untimely use by an actor.