Inko is a statically-typed, safe, object-oriented programming language for writing concurrent programs. Thanks to the use of lightweight processes, concurrency is easily achieved and data races are impossible.
The compiler forces you to handle errors where they may occur, ensuring you never run into unexpected runtime errors again.
The syntax is easy to learn and consistent, instead of using many different keywords and symbols. If you have ever used a programming language that uses curly braces, you'll feel right at home.
import std::stdio::stdout
stdout.print('Hello, world!')
import std::process
import std::stdio::stdout
let proc = process.spawn {
# This will print "Hello" to STDOUT.
match(let message = process.receive) {
as String -> { stdout.print(message) }
else -> {}
}
}
proc.send('Hello')
object List!(T) {
@values: Array!(T)
static def new -> Self {
Self { @values = Array.new }
}
def push(value: T) -> T {
@values.push(value)
}
}
let list = List.new
# This is OK:
list.push(10)
# This will produce a type error:
list.push('oops')
import std::stdio::stderr
def withdraw(euros: Integer) !! String {
euros.negative?.if_true {
throw 'Invalid number of Euros!'
}
}
# "withdraw" might throw, so we must use the
# "try" keyword
try withdraw(euros: 5)
# We can also handle the error, if needed:
try {
withdraw(euros: 5)
} else (error) {
stderr.print(error.message)
}
# "try!" terminates the program upon
# encountering an error:
try! withdraw(euros: 5)
import std::fs::file
import std::stdio::stdout
let txt_file = try! file.read_only('text.txt')
let content = try! txt_file.read_string
stdout.print(content)
import std::fs::file
let readme =
try! file.read_only('README.md')
# This will produce a type error, since
# the file is opened in read-only mode.
readme.write_string('oops')
# This also won't work, because we can
# not remove a read-only file.
readme.remove
import std::test
import std::test::assert
test.group('Integer.+') do (group) {
group.test('Summing two Integers') {
assert.equal(1 + 2, 3)
}
}
test.run
import std::conversion::ToString
import std::stdio::stdout
object Person {
@name: String
static def new(name: String) -> Self {
Self { @name = name }
}
}
impl ToString for Person {
def to_string -> String {
@name
}
}
let person = Person.new('Alice')
# This will print "Alice" to STDOUT:
stdout.print(person)
let numbers = Array.new(10, 20, 30)
# Array.get() returns an Option type.
numbers
.get(2)
.map do (n) { n.to_string } # => Some("30")
# Inko guarantees tail call elimination for
# tail recursive methods, so this method
# doesn't overflow the stack.
def fact(num: Integer, acc = 1) -> Integer {
# This returns from the surrounding method.
num.zero?.if_true { return acc }
fact(num - 1, acc * num)
}
fact(15) # => 1307674368000
import std::stdio::stdout
let mut number = 0
while({ number < 10 }) {
stdout.print('This loop will run 10 times')
number += 1
}
loop {
stdout.print('This is an infinite loop.')
}
import std::stdio::stdout
let numbers = Array.new(10, 20, 30)
# "each" allows us to easily iterate over
# a collection:
numbers.each do (number) {
stdout.print(number)
}
# Using "iter" we can obtain an external,
# composable iterator:
let new_numbers = numbers
.iter
.map do (num) { num * 2 }
.to_array
new_numbers # => Array.new(20, 40, 60)
# This method yields values of type Integer
def numbers => Integer {
yield 10
yield 20
}
let generator = numbers
generator.resume # => Some(10)
generator.resume # => Some(20)
generator.resume # => None
def example(number: Integer) -> String {
match(number) {
1..10 -> { 'Yes!' }
20 -> { 'Oh no!' }
else -> { 'The number is not supported' }
}
}
example(10) # => 'Yes!'
example(20) # => 'Oh no!'
example(50) # => 'The number is not supported'
def example(type: Any) -> String {
match(type) {
as String -> { 'A String' }
as Integer -> { 'An Integer' }
else -> { 'Something else' }
}
}
example('foo') # => 'A String'
example(10) # => 'An Integer'
import std::net::socket::TcpListener
import std::net::socket::TcpStream
import std::stdio::stdout
let listener = try! TcpListener
.new(ip: '127.0.0.1', port: 40_000)
let stream = try! TcpStream
.new(ip: '127.0.0.1', port: 40_000)
try! stream.write_string('hello')
stream.close
let connection = try! listener.accept
let output = try! connection.read_string
connection.close
listener.close
stdout.print(output) # => 'hello'
Inko is a statically-typed language, and supports generic types. The compiler comes with type inference, removing the need for manual type annotations in many places.
The use of static typing prevents a wide variety of bugs, and allows for many optimisations a dynamically-typed language may not be able to perform.
Inko is an object-oriented language. Unlike other object-oriented languages, Inko does not support inheritance. Instead, it relies on composition using traits.
The use of composition instead of inheritance prevents tight coupling between objects, without sacrificing flexibility.
Inko uses a form of checked exceptions, inspired by the article The Error Model by Joe Duffy. Exceptions must be handled where they can occur, and you can't throw exceptions from methods unless they are annotated accordingly.
Inko's error handling model takes the pain out of exception handling, without sacrificing performance or flexibility.
Learn more about error handling in the error handling guide.
For concurrency, Inko uses lightweight processes. These processes can't share memory and are garbage collected independently. Communication between processes is done by sending messages.
The lack of shared memory between processes means you don't have to worry about data races, or a garbage collector stopping your entire program.
Inko uses garbage collection to clean up memory, based on Immix. Each process is garbage collected independently, without the need for pausing the entire program.
To reduce garbage collection timings, the garbage collector performs its work using multiple threads.
The Inko VM is lightweight and fits on a single floppy disk (when stripped of any debugging symbols).
An empty Inko program uses about 5MB of RAM, and takes about 30 milliseconds to run.
Inko is free and open source software, licensed under the Mozilla Public License 2.0.
The MPL 2.0 is not a viral license, meaning you can comfortably use Inko in proprietary projects.
The development of Inko happens in public, instead of behind closed doors. Donations are used exclusively to fund the development of Inko.
Everybody is welcome to contribute: whether it's fixing a bug, improving documentation, or simply telling your friends and colleagues about Inko.
Inko supports Linux, Windows, and macOS. Inko should also work on BSDs such as FreeBSD and OpenBSD, but these platforms are not officially supported at this time. ARM is not supported at this time.
Inko requires a 64-bits architecture, and won't run on a 32-bits architecture.