Primitive Types

Primitive Types

Aiken has 6 primitive types that are built in the language and can be typed as literals: booleans, integers, strings, bytearrays, data and void. The language also includes few base building blocks for associating types together: lists, tuples, options, pairs...

Worry not, we'll see later in this manual how to create your own custom types.

Inline comments are denoted via //. We'll use them to illustrate the value of some expressions in examples given all across this guide.

Bool

A Bool is a boolean value that can be either True or False.

Aiken defines a handful of operators that work with booleans. No doubts that they'll look quite familiar.

OperatorDescriptionPrecedence
==Equality4
&&Logical conjunction (a.k.a 'AND')3
||Logical disjunction (a.k.a. 'OR')2
!Logical negatation (a.k.a 'NOT')1
?Trace if false (see also Troubleshooting)1

Like most languages, && and || are right-associative, which means they don't evaluate their second operand if the first one gives the answer (i.e. short-circuit).

and/or keywords

You'll soon come to realize that long chains of logical operators are quite common when building validators.

True && False && True || False

In this example one could derive the final boolean after thinking for a little but it also requires that you carefully consider how precedence affects the expression grouping. Although this specific example may be simple, in practice those True and False literals would actually be expressions of type Bool. Considering that these kinds of boolean checks are a huge corner stone of validator logic, we've introduced keywords that increase the readability of these so called logical operator chains.

and {
  True,
  False,
  or {
    True,
    False,
  }
}

With these keywords, the grouping becomes immediately obvious at a glance resulting in more readable and maintainable logical operator chains. This shines especially when composing large numbers of logical operators (i.e. four or more) but is less impactful when there are one or two logical operators.

Int

Aiken's only number type is an arbitrary sized integer. This means there is no underflow or overflow.

42
14
1337

Literals can also be written with _ as separators to enhance readability:

1_000_000

Aiken also supports writing integer literals in other bases than decimals. Binary, octal, and hexadecimal integers begin with 0b, 0o, and 0x respectively.

0b00001111 == 15
0o17 == 15
0xF == 15

Aiken has several binary arithmetic operators that work with integers.

OperatorDescriptionPrecedence
+Arithmetic sum6
-Arithmetic difference6
/Whole division7
*Arithmetic multiplication7
%Remainder by whole division7

Integers are also of course comparable, so they work with a variety of binary logical operators too:

OperatorDescriptionPrecedence
==Equality4
>Greater than4
<Smaller than4
>=Greater or equal4
<=Smaller or equal4

Any serialisable type is comparable through == in Aiken. A serialisable type represents any of the primitive type in this guide with the exception of Fuzzer and MillerLoopResult, as well as any user-defined custom data-type made of serialisable types.

Said differently, you can compare bytestrings, booleans, integers, lists, tuples, etc. using the == operator.

ByteArray

A ByteArray is exactly what it seems, an array of bytes. Aiken supports three notations to declare ByteArray literals.

As an array of bytes

First, as list of integers ranging from 0 to 255 (a.k.a. bytes):

#[10, 255]
#[1, 256] // results in a parse error because 256 is bigger than 1 byte

Syntax rules for literal integers also apply to byte arrays. Thus, the following is a perfectly valid syntax:

#[0xff, 0x42]

As a byte string

Second, as a UTF-8 encoded byte string. This is generally how common text strings are represented under the hood. In Aiken, simply use double-quotes for that:

"foo" == #[0x66, 0x6f, 0x6f] == #[102, 111, 111]

As a hex-encoded byte string

Because it is quite common to manipulate base16-encoded byte strings in a blockchain context (e.g. transaction id, policy id, etc..); Aiken also supports a shorthand syntax for declaring bytearrays as an hexadecimal string.

Behind the scene, Aiken decodes the encoded string for you and stores only the raw bytes as a ByteArray. This is achieved by prefixing a double-quotes byte string with a #, like so:

#"666f6f" == #[0x66, 0x6f, 0x6f] == #[102, 111, 111] == "foo"

Note how this is different from:

"666f6f" == #[0x36, 0x36, 0x36, 0x66, 0x36, 0x66] == #[54, 54, 54, 102, 54, 54]

List

Lists are ordered collections of values. They're one of the most common data structures in Aiken.

Unlike tuples, all the elements of a List must be of the same type. Attempting to make a list using multiple different types will result in a type error.

[1, 2, 3, 4]  // List<Int>
 
["text", 3, 4]  // Type error!

Inserting at the front of a list is very fast, and is the preferred way to add new values.

[1, ..[2, 3]] // [1, 2, 3]

Note that all data structures in Aiken are immutable so prepending to a list does not change the original list. Instead it efficiently creates a new list with the new additional element.

let x = [2, 3]
let y = [1, ..x]
 
x // [2, 3]
y // [1, 2, 3]

Tuple(s)

Aiken has tuples which can be useful for grouping values. Unlike lists, each element in a tuple can have a different type.

(10, "hello") // Type is (Int, ByteArray)
(1, 4, [0]) // Type is (Int, Int, List<Int>)

Long tuples (i.e. more than 3 elements) are usually discouraged. Indeed, tuples are anonymous constructors, and while they are quick and easy to use, they often impede readability. When types become more complex, one should use records instead (as we'll see later).

Elements of a tuple can be accessed using the dot, followed by the index of the element (ordinal). So for example:

let point = (14, 42, 1337, 0)
let a = point.1st
let b = point.2nd
let c = point.3rd
let d = point.4th
(c, d, b, a) // (1337, 0, 42, 14)

Pair

Aiken has a specific type for representing a pair of values of two not-necessarily equal types. Thus a Pair<a, b> is akin to a 2-tuple (a, b). Like for tuples, you can access elements of a pair using the ordinal syntax:

let foo = Pair(14, "aiken")
 
foo.1st == 14
 
foo.2nd == "aiken"

So why have another type specifically for pairs? The answer is two folds:

  1. This is a specific kind of term that also exists on the underlying Plutus Virtual Machine. Hence, given Aiken's proximity to that machine's representation, it is useful, if not simply necessary, to have ways to represent those when needed.

  2. There's an ambiguity at the contract's boundary regarding how to serialise lists of pairs in CBOR. One way is to use a CBOR array of arrays (of two elements). And the other is to use a CBOR map. While the former is more intuitive, it is in fact the latter that end up being used by the ledger for many internal types. In the first versions of Aiken, the compiler would implicitly treat list of pairs as CBOR map in serialisation functions to mimic the ledger. This has proven inconvenient and confusing in many scenarios involving user-defined data-types, hence the introduction of pairs.

So as a rule of thumb, unless you specifically want to serialize external types (in datums and redeemers) as CBOR maps, you probably never want to use a Pair<a, b> and can stick to a 2-tuple (a, b).

Option

We define Option as a generic type with two constructors (see also custom types for details about this syntax):

type Option<a> {
  Some(a)
  None
}

The type parameter a indicates that an Option may be inhabited by any other type. Functions may manipulate it without making assumptions on what this type is, or may require the type to be instantiated to a particular concrete type.

Options are used in situation where function must return optional values. For example:

/// Extract the first element of a list, if any.
fn get_head(xs: List<a>) -> Option<a> {
  when xs is {
    [] -> None
    [head, ..] -> Some(head)
  }
}
 
/// Ensures a number is strictly positive.
fn to_positive(n: Int) -> Option<Int> {
  if n <= 0 {
    None
  } else {
    Some(n)
  }
}

The Option type is readily available in Aiken; it is part of the types and constructors available by default. Don't hesitate to use it!

Never

In some rare cases, it is needed to refer to an Option that can only ever be None. This is the case in some parts of the standard library and mainly due to unforeseen bugs in the ledger and backward compatibility concerns. For these scenarios, we introduce a Never type, which has a single constructor of the same name. It is identical in all points to None and serialises down to the same binary structure. Said differently, we have:

let some: Data = None
let never: Data = Never
some == never

Ordering

Ordering (opens in a new tab) type is helpful when comparing two values of same type. It is defined as:

pub type Ordering {
  Less
  Equal
  Greater
}

And standard library (opens in a new tab) often defines compare function corresponding to different types, for instance, to compare two ByteArrays one can use this bytearray.compare (opens in a new tab) function.

Void

Void is a type representing the nullary constructor, or put simply, the absence of value. It is denoted Void as a type and constructor. Fundamentally, if you think in terms of tuples, Void is a tuple with no element in it.

Void is useful in certain situations, but because in Aiken everything is a typed expression (there's no "statement"), you'll rarely end up in a situation where you need it.

Data

A Data is an opaque compound type that can represent any possible user-defined type in Aiken. We'll see more about Data when we cover custom types. In the meantime, think of Data as a kind of wildcard that can possibly represent any serialisable value.

This is useful when you need to use values from different types in an homogeneous structure. Any user-defined type can be cast to a Data, and you can try converting from a Data to any custom type in a safe manner. Besides, several language builtins only work with Data as a way to deal with polymorphism.

String

In Aiken text strings can be written as text surrounded by double quotes, prefixed with @.

@"Hello, Aiken!"

They can span multiple lines.

@"Hello
Aiken!"

Under the hood text strings are UTF-8 (opens in a new tab) encoded binaries and can contain any valid unicode.

@"🌘 アルバイト Aiken 🌒"
⚠️

Beware the use case for strings is extremely narrow in Aiken and on-chain code. They are only used for tracing, a bit like labels attached to specific execution paths of your program. You can't find them in the interface exposed by your validator, for example. So most of the time, you probably want to use a ByteArray instead and only resort to String for debugging.

Advanced

💡

If you're just getting started with Aiken, you can probably skip the following section as it describes some more advanced types and behaviors. Feel free to come back to it in due time.

Pairs

An associative list (a.k.a Pairs) is a mere type alias such that: type Pairs<a, b> = List<Pair<a, b>>. It exists as a convenience since pairs are mainly present in validator's script contexts under this form. In addition to functions for List which are also all usable on Pairs, the stdlib also provides a dedicated module of helpers (opens in a new tab) specifically crafted for associative lists.

PRNG & Fuzzer

Aiken has an integrated property-based testing framework. Yes, you read that right. Property-based test is a first-class citizen here. If you want to read more about this, we strongly recommend looking at our guide about property-based test; it should teach you everything you need to get started with the framework.

To support this framework, the prelude comes with two pre-defined types. PRNG stands for Pseudo-Random Number Generator. Its definition is opaque and matters only to the internals of the test framework. If you're still interested about the details, head towards the aiken-lang/fuzz (opens in a new tab)!

Similarly, the Fuzzer is an interface for building random generators. It offers a base set of primitives for a variety of types, and compose nicely with one another.

G1Element, G2Element & MillerLoopResult

Specific to the use of BLS12-381 cryptographic primitives, the G1Element, G2Element and MillerLoopResult capture the types of various operands and return values of some builtin functions. Their usage is agnostic to Aiken itself and described in more details in CIP-0381: Plutus support for Pairings over BLS12-381 (opens in a new tab).