Language Tour
Primitive Types

Primitive Types

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

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
&&Logical conjunction (a.k.a 'AND')3
||Logical disjunction (a.k.a. 'OR')2
==Equality4
!Logical negatation (a.k.a 'NOT')1
?Trace if false (see also Troubleshooting)1

Like most languages, && and || 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

ByteArray

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

1 - 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]

2 - 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]

3 - 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]

Tuples

Aiken has tuples which can be useful for grouping values. 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)

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]

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 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.