Skip to content

Basic types

Inko has a variety of basic types, which we will cover in this chapter. In addition to these types you can define your own types, which we will cover separately.


The Integer type is used for numbers without fractions, such as 10 and 9000. The Integer type is an arbitrary precision integer type, meaning it can store values of any type; both positive and negative. There are no types for different integer sizes, such as 16 and 32 bits integers. Here are a few examples of Integer values:


To make integer literals (the numbers above) easier to read, you can separate the digits using an underscore:


The use of these underscores has no impact on the value, it's purely cosmetic.

You can also use hexadecimal notation for integers:



The Float type is used for numbers with fractions. The Float type follows the IEEE 754 floating-point standard, and is a double-precision floating point type. Here are a few examples:



For text there is the String type, which is an immutable UTF-8 encoded string type. You can create a String using single quotes ('), double quotes ("), or backticks (```):


Single quoted strings

When using single quotes, escape sequences such as \n and \t are literal characters. In other words, '\t' is a String containing two bytes: 92 (\) and 116 (t).

Double quoted strings

When using double quotes, Inko translates these sequences into other characters. For example, the String "\t" is a String containing only a single byte: 9, the tab character byte. When using double quotes, you can use the following escape sequences:

Sequence Character represented
\t Horizontal tab
\n Newline
\r Carriage return
\e Escape character (e.g. for ANSI escape sequences)
\0 NULL byte

You can also escape quotes themselves to treat them as literal quotes. For example, to create a single-quoted string containing a single quote you'd use the following:


This also works for double quotes:


Template strings

Inko also supports template strings. Template strings can contain escape sequences like double-quoted strings, and also support embedded expressions. For example:

let number = 42

`The number is: {number}`

Here {number} is an embedded expression, and its result will be converted to a String. For this to work, the return value of the expression must implement the trait std::conversion::ToString.

If you want to include a literal {, you must escape it like so:

let number = 42

`The number is: \{number}`

You don't need to escape the closing }.


The boolean types are True and False, which are instances of the Boolean type. Most languages use dedicated keywords for booleans, such as true and false in Ruby. In Inko, booleans are just constants.


The Array type stores values in a contiguous order. Unlike other languages, there is no special syntax for creating an Array. Instead, you just send the new message to the Array type and pass it the values you wish to store in the Array:             # An empty array, 20, 30) # An array with 3 values

You can store any value in an Array, provided it's compatible with the other values in the Array. In other words, the Array type is a generic type; but more on that later. For now, know that code such as this isn't valid:, 'foo', 20.5) # Value types are not compatible, so this will not work

Byte arrays

While you can store bytes as a sequence of Integer values in an Array, this is not efficient as every Integer requires 8 bytes of space. For this Inko provides the ByteArray type, which stores bytes more efficiently. You create a ByteArray just like you create an Array:             # An empty ByteArray, 20, 30) # A ByteArray containing 3 bytes

Since a ByteArray stores bytes, the smallest Integer value it allows is 0, while the largest allowed value is 255. This means the following is not valid:

Hash maps

The Map type is Inko's built-in hash map type. Similar to the Array type there is no special syntax for hash maps, you just create them by sending messages:

let map =

map['key'] = 'value'

This will create a Map, then set the 'key' key to the value 'value'. You can also send the set message to Map, which will return the Map itself. This is useful when you want to create a Map and set keys right away:'key1', 'value1').set('key2', 'value2')

Just like the Array type, a Map is generic. For both keys and values you can use any type, provided the keys and values are compatible with those already set. This means the following is invalid:

let map =

map['foo'] = 'bar'
map[10] = 'baz'

Here 10 is an Integer, which is not compatible with the type of the 'foo' key (a String).


The type Nil is used to represent the lack of a value. This type is an instance of NilType, though direct use of NilType is rare. To use Nil, just refer to it as-is:

let nothing = Nil

The Nil type is not the same as the NULL type seen in other languages. Like any other object you can send messages to it. How this works and what that results in is covered in a separate chapter.

Block types

A block is a method, closure, or lambda.

To create a method, use the def keyword:

def hello {
  # ...

Method names are alphanumerical, can contain underscores, and may end in a single question mark (?). Method names can also be single binary operators such as + and /, and the special indexing operators [] and []=. Here are just a few examples:

def [](index: Integer) {}
def []=(index: Integer, value: Integer) {}
def /(value: Integer) {}
def foo {}
def foo_bar {}
def valid? {}

To create a closure, use the do keyword:

do {
  # ...

Closures can capture outer variables:

let number = 10

do {
  number # => 10

You can also leave out the do and use curly braces, provided the closure has no arguments, no explicit return type, and no explicit throw type:

  # This is also a closure

To create a lambda, use the fn keyword:

fn {
  # ...

Lambdas can't capture variables. This means lambdas are basically anonymous methods that you can pass around.


Classes are created using the class keyword:

class Person {
  # ...

You can create instances of classes like so:

Person {} # This creates a new Person instance

If a class defines one or more attributes, you must specify them when creating an instance:

class Person {
  @name: String

Person { @name = 'Alice' }


Traits are types used in Inko to compose behaviour. Traits are like interfaces found in other languages, with the added feature of being able to define default method implementations. To create a trait, use the trait keyword:

trait ToString {
  # ...

Inko uses nominal typing, meaning a trait is only implemented for an object when the developer implements it explicitly. How to do this will be covered separately.

Self types

The type Self is a special type that can be used in a few places, such as type signatures. The use of this type is best explained using a simple example:

class Number {
  def add(other: Self) -> Self {
    # ...

Here the argument of other is defined to be of type Self, and so is the return type of the add method. Since this method is defined on the type Number, using Self is like saying "This is an instance of Number".

Using Self is useful in traits where you may not know the type of the object that implements the trait, and you just want something that is of that same type. For example, most operators specify that their argument is of type Self, ensuring that 10 + 5 is valid but 10 + "foo" is not.


The type Never is used to signal that something will never happen, such as when a method will never return (because it terminates the program for example). This type can only be used in type signatures, such as when defining a method's return type.


The Any type is a special type that signals something can be a value of any type. You can't send messages to an Any type, nor can you pass it to something that expects a type other than Any. This type is used by the compiler in certain cases when a more specific type can't be inferred.