Inko 0.17.0 released
We're pleased to announce the release of Inko 0.17.0. This release includes support for inlining method calls, changes to built-in concurrency types, support for working with CSV files, and more.
After releasing 0.17.0, a bug resulting in a compiler crash was revealed. We published a fix for this bug in version 0.17.1 and recommend users to use that version instead of 0.17.0.
Table of contents
- Inlining of method calls
- Futures, Promises and Channels
- New implementations for various IO APIs
- Warnings for unused imports
- Checking if a TTY is used
- Chaining iterators
- Buffered writes
- Parsing and generating CSV data
- Building the compiler on Alpine now works (again)
- Bug fixes
For the full list of changes, refer to the changelog.
A special thanks to the following people for contributing changes included in this release:
Inlining of method calls
This release introduces support for inlining method calls. Inlining allows eliminating the cost of the method call, and enables the application of other (future) optimizations. Method calls are inlined when meeting one of the following conditions:
- The method is determined to be small enough using a heuristic.
- The method is defined using the
inline
keyword.
The exact heuristic used is unspecified and subject to change. The heuristic is used to determine a rough size estimate of a method, which is then used to determine if there's room left to inline a method into its caller. The current threshold applied to each method is on the conservative end as we've yet to include optimizations that take advantage of inlining, but this is likely to change in the future.
If a method is defined using the inline
keyword, it's always inlined
regardless of its size, unless the method is a recursive method. This keyword
is used by the various operator methods (e.g. Int.+
) to ensure they're always
inlined, resulting in them compiling to simple machine instructions instead of
requiring a method call.
After inlining method calls, the compiler checks for methods that are now no longer called and removes them. This ensures that if a method is inlined into all its call sites, it isn't included in the final executable.
Inlining is enabled by default, and disabled when using inko build --opt=none
.
You can read more about inlining and the other optimizations the compiler applies in the new Compiler optimizations page in the manual.
Futures, Promises and Channels
In 0.11.0 the use of the
Future
type was replaced with a Channel
type. In 0.17.0, futures are
re-introduced but in a different and much better way. The new setup consists of
two types: Future
and Promise
.
A Future
is used to read a value produced asynchronously while a Promise
is
used to write the value. Writing to a Promise
or reading from a Future
takes over ownership of the value and thus one can do so only once. When reading
from a Future
, the calling process is suspended until a value is available:
import std.sync (Future)
class async Main {
fn async main {
match Future.new {
case (future, promise) -> {
promise.set('hello')
future.get # => "hello"
}
}
}
}
The Channel
type still exists but is no longer a type provided by the runtime library,
instead it's built on top of the new Future
and Promise
types and backed by
an Inko process:
import std.sync (Channel)
class async Main {
fn async main {
let chan = Channel.new
chan.send('hello')
chan.receive # => "hello"
}
}
In this new setup, the Future
and Promise
types are the main types to
communicate results back to the process that sent a message. The Channel
type
in turn is meant for cases where you have many Future
values you want to
resolve and you don't want to wait for all of them to be resolved before
moving on.
The new Future
and Promise
types are implemented entirely in Inko, instead
of relying on Rust functions provided by the runtime library. This means they
support all types, including those that are not the size of a word, something
the old Channel
type didn't support.
New implementations for various IO APIs
Various IO APIs, such as those provided by the std.fs.file
and
std.net.socket
modules are now implemented entirely in Inko, instead of
relying on functions provided by the runtime library written in Rust. For
example, std.fs.file.ReadOnlyFile
was implemented on top of Rust's File
type, but is now written entirely in Inko.
This change gives us greater control over the implementations of these APIs and ensures Rust semantics/behaviours won't leak into Inko. In certain cases this also results in a more efficient implementation.
Refer to the following issues for more details:
- Implement file IO using pure Inko
- Implement socket IO using pure Inko
- Implement std.sys.Command in pure Inko
Warnings for unused imports
In 0.14.0 we added support for producing compile-time warnings for unused variables. In 0.17.0 we've extended this to also produce warnings for unused imported symbols. For example, take this program:
import std.stdio (Stderr, Stdout)
class async Main {
fn async main {
Stdout.new.print('hello')
}
}
Here Stderr
is unused and so the compiler produces the following warning:
test.inko:1:19 warning(unused-symbol): the symbol 'Stderr' is unused
Checking if a TTY is used
The standard library types Stdio
, Stdout
and Stderr
now support a
terminal?
method that returns true
if the stream is connected to a
terminal/TTY. For example:
import std.stdio (Stdin, Stdout)
class async Main {
fn async main {
Stdout.new.print(Stdin.new.terminal?.to_string)
}
}
This writes true
to STDOUT when run in an interactive terminal, and false
otherwise (e.g. when running the program as part of a pipeline).
Chaining iterators
Thanks to Ryan Frame, the standard library Iter
type supports
chaining of two iterators together using Iter.chain
:
import std.stdio (Stdout)
class async Main {
fn async main {
let a = [10, 20, 30]
let b = [40, 50, 60]
let out = Stdout.new
a.into_iter.chain(b.into_iter).each(fn (val) { out.print(val.to_string) })
}
}
This produces the following:
10
20
30
40
50
60
Buffered writes
This release introduces the standard library type
std.io.BufferedWriter
.
This type implements std.io.Write
and buffers writes, reducing the amount of underlying IO operations:
import std.io (BufferedWriter)
import std.stdio (Stdout)
class async Main {
fn async main {
BufferedWriter.new(Stdout.new).print('hello')
}
}
Parsing and generating CSV data
The newly added module
std.csv
provides
types for parsing and generating CSV data, conforming to RFC
4180. For example, parsing a CSV stream
is done as follows:
import std.csv (Parser)
import std.fmt (fmt)
import std.io (Buffer)
import std.stdio (Stdout)
class async Main {
fn async main {
let parser = Parser.new(Buffer.new('foo,bar'))
let rows = []
parser.each(fn (result) { rows.push(result.get) })
Stdout.new.print(fmt(rows))
}
}
This produces the following output:
[["foo", "bar"]]
Generating CSV data is done as follows:
import std.csv (Generator)
import std.stdio (Stdout)
class async Main {
fn async main {
Generator.new(Stdout.new).write(['foo', 'bar'])
}
}
This produces the following output:
foo,bar\r\n
Building the compiler on Alpine now works (again)
A long time ago, Inko supported Alpine. Since the switch to compiling to machine code using LLVM, Alpine wasn't supported due to the llvm-sys crate (which we use for interfacing with LLVM) not building on Alpine. This problem was fixed by this merge request. This means that starting with Inko 0.17.0, Alpine is supported once more.
Bug fixes
This release includes the following bug fixes:
- Fix unreliable ordering when specializing types
- Fix formatting of binding patterns
- Escape ampersands in MIR Graphviz output
- Fix building the compiler on Alpine/musl
- Limit the number of threads for subprocess tests
- Refactor messages to support non-word arguments
- Fix capturing of C structures
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 as this allows us to continue working on Inko full-time.