You're looking at the documentation for the "main" branch, click here to view the documentation for the latest stable release.

Control flow

Inko has the following control flow constructs: if, and, or, while, loop, try, and throw.

Conditionals

For conditionals we use if:

import std.stdio (Stdout)

type async Main {
  fn async main {
    let out = Stdout.new
    let num = 42

    if num == 42 { out.print('yes') } else { out.print('no') }
  }
}

When you run this program, the output is "yes". If you change the value of num to e.g. 50, the output is instead "no".

Inko also supports else if like so:

import std.stdio (Stdout)

type async Main {
  fn async main {
    let out = Stdout.new
    let num = 50

    if num == 42 {
      out.print('A')
    } else if num == 50 {
      out.print('B')
    } else {
      out.print('C')
    }
  }
}

The output of this program is "B".

To perform boolean AND and OR operations, you can use the and and or keywords:

import std.stdio (Stdout)

type async Main {
  fn async main {
    let out = Stdout.new
    let num = 50

    if num == 42 or num == 50 {
      out.print('A')
    } else if num >= 10 and num <= 20 {
      out.print('B')
    } else {
      out.print('C')
    }
  }
}

This prints "A" if you run the program as-is, and "B" if you change num to 20.

Loops

Inko has three types of loops: for, while and loop. Loop iteration is controlled using the break and next keywords.

for

The for expression is used to iterate over values provided by an iterator. For example, to iterate over the values in an array (taking over ownership of the array in the process):

import std.stdio (Stdout)

type async Main {
  fn async main {
    let out = Stdout.new

    for num in [10, 20, 30] {
      out.print(num.to_string)
    }
  }
}

for loops are just syntax sugar, and the above for loop is compiled to the following:

let __iter = [10, 20, 30].into_iter

loop {
  match __iter.next {
    case Some(num) -> print(num)
    case _ -> break
  }
}

A for loop supports any collection/iterator that implements the into_iter method, which in turn is expected to return a type that implements the std.iter.Iter trait.

for loops can use pattern matching to match against specific values returned by next:

import std.stdio (Stdout)

type async Main {
  fn async main {
    let out = Stdout.new

    for (str, _) in [('foo', 10), ('bar', 20)] {
      out.print(str)
    }
  }
}

If the pattern doesn't match the value returned by next, iteration stops:


import std.stdio (Stdout)

type async Main {
  fn async main {
    let out = Stdout.new

    for (str, 10) in [('foo', 10), ('bar', 20)] {
      out.print(str)
    }
  }
}

This prints foo to STDOUT then iteration stops, as the pattern only matches the first value in the array.

while

A while loop is a loop that repeats itself for as long as its condition evaluates to true:

import std.stdio (Stdout)

type async Main {
  fn async main {
    let out = Stdout.new
    let mut num = 0

    while num < 10 {
      out.print(num.to_string)
      num += 1
    }
  }
}

The output of this program is as follows:

0
1
2
3
4
5
6
7
8
9

loop

The loop keyword is used to create a loop that runs indefinitely. The following program loops indefinitely, printing an ever increasing number every 500 milliseconds:

import std.process (sleep)
import std.stdio (Stdout)
import std.time (Duration)

type async Main {
  fn async main {
    let out = Stdout.new
    let mut num = 0

    loop {
      out.print(num.to_string)
      num += 1
      sleep(Duration.from_millis(500))
    }
  }
}

break and next

You can control the iteration of a loop using the next and break keywords: next jumps to the start of the next loop iteration, while break jumps out of the inner-most loop:

import std.stdio (Stdout)

type async Main {
  fn async main {
    let out = Stdout.new

    loop {
      out.print('hello')
      break
    }
  }
}

This program prints "hello", then stops the loop.

throw

throw takes an expression and wraps it in the Error constructor of the std.result.Result enum, then returns it:

fn example -> Result[Int, String] {
  throw 'oh no!'
}

This is the equivalent of the following:

fn example -> Result[Int, String] {
  return Result.Error('oh no!')
}

The throw keyword is only available in methods of which the return type is a Result.

try

try takes an expression of which the type is either std.result.Result or std.option.Option, and gets it. If the value is a Result.Error or an Option.None, the value is returned as-is.

Consider this example of using try with an Option value:

let value = Option.Some(42)

try value

This is the equivalent of:

let value = Option.Some(42)

match value {
  case Some(v) -> v
  case None -> return Option.None
}

And when using a Result:

let value = Result.Ok(42)

try value

This is the equivalent of:

let value = Result.Ok(42)

match value {
  case Ok(v) -> v
  case Error(e) -> return Result.Error(e)
}

Conditional moves

If a variable is dropped conditionally, it's not available afterwards:

let a = [10]

if something {
  let b = a
}

# `a` _might_ be moved at this point, so we can't use it anymore.

The same applies to loops: if a variable is moved in a loop, it can't be used outside the loop:

let a = [10]

loop {
  let b = a
}

Any variable defined outside of a loop but moved inside the loop must be assigned a new value before the end of the loop. This means the above code is incorrect, and we have to fix it like so:

let mut a = [10]

loop {
  let b = a

  a = []
}

We can do the same for conditions:

let mut a = [10]

if condition {
  let b = a

  a = []
}

# `a` can be used here, because we guaranteed it always has a value at this
# point

If a value is moved in one branch of a condition, it remains available in the other branches:

let a = [10]

# This is fine, because only one branch ever runs.
if foo {
  let b = a
} else if bar {
  let b = a
}