Primitives¶
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
None
to 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 ofNone
. - 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¶
The 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 eithertrue
orfalse
.ISize
,ILong
,I8
,I16
,I32
,I64
,I128
. Signed integers of various widths.USize
,ULong
,U8
,U16
,U32
,U64
,U128
. Unsigned integers of various widths.F32
,F64
. Floating point numbers of various widths.
ISize
/USize
correspond to the bit width of the native type size_t
, which varies by platform. ILong
/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 I32
/U32
for this.
Primitive initialisation and finalisation¶
Primitives can have two special functions, _init
and _final
. _init
is called before any actor starts. _final
is called after all actors have terminated. The two functions take no parameter. The _init
and _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.