Hello, concurrency!

Let's make printing "Hello, world!" a little more exciting by performing work concurrently. We'll start with creating the file hello.inko with the following contents:

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

class async Printer {
  fn async print(message: String) {
    let _ = STDOUT.new.print(message)
  }
}

class async Main {
  fn async main {
    Printer {}.print('Hello')
    Printer {}.print('world')
    sleep(Duration.from_millis(500))
  }
}

This program prints "Hello" and "world" concurrently to the terminal, then waits 500 milliseconds for this to complete.

To showcase this, run the program several times as follows:

inko run hello.inko

The output may change slightly between runs: sometimes it will print "Hello" and "world" on separate lines, other times it may print "Helloworld", "worldHello" or "world" and "Hello" on separate lines.

Explanation

Inko uses "lightweight processes" for concurrency. Such processes are defined using the syntax class async, such as class async Printer { ... } in our program.

We can create instances of these processes using the syntax Printer {}. For such a process to do anything, we must send it a message. In our program we do this using print(...), where "print" is the message, defined using the fn async syntax. The details of how this works, what to keep in mind, etc, are covered separately.

The sleep(...) line is needed such that the main process (defined using class async Main) doesn't stop before the Printer processes print the messages to the terminal.

Stopping right away when output is produced

Instead of waiting for a fixed 500 milliseconds, we can change the program to stop right away when the output is produced. We achieve this by changing the program to the following:

import std.stdio.STDOUT

class async Printer {
  fn async print(message: String, channel: Channel[Nil]) {
    let _ = STDOUT.new.print(message)

    channel.send(nil)
  }
}

class async Main {
  fn async main {
    let channel = Channel.new(size: 2)

    Printer {}.print('Hello', channel)
    Printer {}.print('world', channel)
    channel.receive
    channel.receive
  }
}

What we changed here is that we're using the Channel type, and instead of sleeping we wait for two messages to be received using channel.receive. The Printer types are changed to send nil to the channel when they are finished. The combination of the two results in the Main process waiting for both Printer processes to write their output, then it stops.