Inko 0.15.0 released

Published on

We're pleased to announce the release of Inko 0.15.0. This release includes support for automatically formatting source code, generating documentation from source code, support for handling Unix signals, and much more.

Table of contents

For the full list of changes, refer to the changelog.

A special thanks to the following people for contributing changes included in this release:

We'd like to thank the NLnet foundation for sponsoring part of the work that went into this release.

Automatic formatting of source code

Thanks to the NLnet foundation for sponsoring the development of this feature.

Inko 0.15.0 introduces the inko fmt command, capable of formatting one or more source files according to the Inko style guide. This command can either be used manually from the command-line, or from your editor using the appropriate plugin (e.g. conform.nvim for NeoVim). For example, we can automatically format messy code such as this:

import std.stdio(STDOUT)
     import std.net.ip(IpAddress)
  class async Main {
       fn async main {
let addr = IpAddress.v4(
      127,0,0,
      1
    )
    STDOUT.new
      .print(addr
        .to_string)
  }
}

Into this:

import std.net.ip (IpAddress)
import std.stdio (STDOUT)

class async Main {
  fn async main {
    let addr = IpAddress.v4(127, 0, 0, 1)

    STDOUT.new.print(addr.to_string)
  }
}

Inko's formatter is an opinionated formatter and offers no configuration settings to adjust, ensuring that every project using inko fmt uses the same style. Some might argue in favour of being able to configure certain aspects (e.g. the line length), but the ability to do so only leads to constant bike-shedding.

Users can still control the formatting somewhat, as the formatter retains empty lines in expression bodies (collapsing multiple empty lines into a single one). This allows you to visually separate certain blocks of code if deemed beneficial.

See the documentation for more details on how to use the new inko fmt command.

See commit 215b63d for more details, and this article for details on the algorithm used by the formatter.

Generating documentation from source code

Thanks to the NLnet foundation for sponsoring the development of this feature.

Starting with Inko 0.15.0, you can generate documentation from your source code. The inko doc command is used to generate a list of JSON files containing information about the various symbols (e.g. classes) of a program. A separate program is then used to convert these JSON files into the desired format. idoc is an official program used to convert these JSON files to a static HTML website.

idoc is also used to generate the documentation for Inko's standard library, which is found at the following places:

To use idoc in your own project, run idoc and the resulting website is found at ./build/idoc/public. To include documentation for dependencies, run idoc --dependencies. In the future we'll also provide a platform to host documentation for Inko packages, such that you don't need to generate this yourself. See this issue for more details.

For more information about generating documentation (such as how to install idoc), refer to the documentation, or take a look at commit cefa664.

Support for handling Unix signals

Inko 0.15.0 introduces the type std.signal.Signal. This type is used to block the current process (not the OS thread) until a given Unix signal is received. For example, to wait for the SIGUSR1 signal, one uses it as follows:

import std.signal (Signal)

class async Main {
  fn async main {
    Signal.User1.wait
  }
}

Signal support is implemented by using a dedicated thread that calls sigwait(3), with all the registered signals, rescheduling the appropriate processes when their desired signal is received.

For more information, refer to the following:

Parsing of command-line options

The standard library now includes the module std.optparse, used for parsing command-line options. For example, to parse a --help option one writes the following:

import std.env
import std.optparse (Options)

class async Main {
  fn async main {
    let opts = Options.new

    opts.flag('h', 'help', 'Show this help message')

    let matches = opts.parse(env.arguments).get
  }
}

See commit 0fb6d0a for more details.

Support for compile-time variables

If a constant is defined using let pub and of type String, Int or Bool, you can overwrite its value at compile-time using inko build --define. This allows you to change certain parts of a program without having to change its source code, such as the directory it loads certain files from. For example:

let pub PATH = '/usr/share/example'

class async Main {
  fn async main {}
}

To change PATH to /usr/local/share/example, we compile the program as follows:

inko build --define main.PATH=/usr/local/share/example

For more information, refer to the documentation and commit 3298ae2 for more details.

Improvements to interacting with C code

The Main process now always runs on the main OS thread. This makes it possible to interact with C code that uses thread-local state and expects code to run on the same thread, such as most GUI frameworks.

In addition, module methods defined using the extern keyword can be passed as callbacks to C functions using the syntax mut method_name. For example:

# This guarantees this method uses the C calling convention.
fn extern example(value: Int) -> Int {
  value
}
class async Main {
  fn async main {
    let pointer_to_method = mut example
  }
}

Here pointer_to_method is a pointer to the example method, not a borrow.

See commits 0f68a92 and 01bd04e for more details.

Renaming of error handling methods

The following methods are renamed:

  • unwrap (e.g. Option.unwrap) is renamed to get (e.g. Option.get)
  • unwrap_or is renamed to just or
  • unwrap_or_else is renamed to or_else
  • expect is renamed to or_panic

This means that instead of this:

let x = Option.Some(42)

x.expect('a Some is expected')

You now write this:

let x = Option.Some(42)

x.or_panic('a Some is expected')

The drop order of fields and constructor arguments is changed

Instead of dropping fields and constructor arguments in definition order, they're now dropped in reverse-definition order to match the order used when dropping local variables. If you are experiencing drop panics when upgrading to Inko 0.15.0, you'll likely need to change the definition order of the relevant fields to resolve this.

See commit 9ce4f9e for more details.

TCP_NODELAY is enabled for TCP sockets

When using std.net.socket.TcpClient and std.net.socket.TcpServer, the TCP_NODELAY option is set automatically. This option is enabled because not enabling it can lead to latency issues when using TCP sockets. In the rare case that this option must be disabled, one can do so using std.net.socket.Socket.no_delay=.

See commit 740682c and this article for more details.

Syntax changes

Strings

Whitespace in string literals can no longer be escaped like so:

let x = 'foo \
bar'

Before 0.15.0 this would result in the string value foo bar, but starting with 0.15.0 this is now a syntax error. The reason for this is that the presence of these escapes complicates the parser too much, it makes automatic code formatting difficult, and it's not that useful as you can do the following instead:

let x = 'foo ' + 'bar'

See commit dc87e18 for more details.

Imports

The import syntax is changed such that a . is no longer needed between the module and the symbol list. In addition, symbols must always be surrounded by parentheses, even when importing only a single symbol:

# Before:
import a.b.c.A
import a.b.c.(A, B)

# After:
import a.b.c (A)
import a.b.c (A, B)

See commit 41a0b3e for more details.

Class literal syntax

The syntax to create an instance of a class is changed, instead of this:

class Person {
  let @name: String
}

Person { @name = 'Alice' }

You write this instead:

class Person {
  let @name: String
}

Person(name: 'Alice')

The old syntax is still supported and inko fmt updates it to the new syntax automatically, but support will be removed in the next release.

See commit b7bcf46 for more details.

Trailing closure syntax

Inko supported the following syntax for closures, provided the closure is the last argument:

foo(10, 20) fn { ... }

Support for this syntax will be removed in 0.16.0, and inko fmt changes it to the following:

foo(10, 20, fn { ... })

This new syntax makes both parsing and formatting (much) simpler compared to the old syntax.

LLVM 16 is now required

Inko 0.15.0 requires LLVM 16, and no longer supports LLVM 15. We plan to upgrade to LLVM 17 as part of the next release.

See commit a35d4ec for more details.

Following and supporting Inko

If Inko sounds like an interesting language, consider joining the Discord server. You can also follow along on the /r/inko subreddit. If you'd like to support the continued development of Inko, please consider donating using GitHub Sponsors.