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

class 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

class 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

class 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 two types of loops: unconditional loops which use the loop keyword, and conditional loops that use the while keyword.

Here we use a conditional loop to print a number 10 times:

import std.stdio.STDOUT

class 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

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

class 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))
    }
  }
}

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

class 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 unwraps 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
}