Inko – Safe and Concurrent Object-Oriented Programming Language
The Inko programming language
A language for building concurrent software with confidence
Inko makes it easy to build concurrent software, without having to worry about
unpredictable performance, unexpected runtime errors, data races, and type
errors.Inko features deterministic automatic memory management, move semantics, static
typing, type-safe concurrency, efficient error handling, and more. Get started Discord GitHubimport std.fs.file (ReadOnlyFile)

class async Main {
  fn async main {
    # or_panic() gets the Result value, panicking 
    # with the given error message when 
    # encountering a `Result.Error`.
    let file = ReadOnlyFile
      .new('README.md'.to_path)
      .or_panic("the file doesn't exist")

    let bytes = ByteArray.new

    file
      .read_all(bytes)
      .or_panic('failed to read the file')

    bytes.to_string # => "# README ..."
  }
}
class async Main {
  fn async main {
    let numbers = [10, 20, 30]

    numbers
      .opt(2)
      .map(fn (n) { n.to_string }) # => Option.Some('30')
  }
}
fn moves {
  let numbers = [10, 20, 30]

  # This is OK, and returns a `ref 10`.
  numbers.get(0)

  let nums = numbers

  # This is no longer OK, as `numbers` gave up ownership to
  # `nums`.
  numbers.get(0)
}

fn refs {
  let owned = [10, 20, 30]

  # We can "borrow" a value using references:
  let borrowed = ref owned

  # Both are OK and produce the same value: `ref 10`.
  owned.get(0)
  borrowed.get(0)

  # This isn't OK, as `ref T` doesn't allow mutation:
  borrowed.push(42)

  # For that we need a mutable reference:
  let mutable = mut owned

  mutable.push(42)
}
import std.stdio (Stdout)

class async Main {
  fn async main {
    Stdout.new.print('Hello, world!')
  }
}
class async Main {
  fn async main {
    match Option.Some(42) {
      case Some(number) -> number
      case None -> 0
    }

    # Of course we also support nested patterns:
    match Option.Some((10, 'foo')) {
      case Some((10, message)) -> message
      case _ -> 'oh no!'
    }

    # Guards are also supported:
    match Option.Some(42) {
      case Some(number) if number < 50 -> number
      case _ -> 0
    }
  }
}
import std.test (Tests)

class async Main {
  fn async main {
    let tests = Tests.new

    tests.test('Adding two integers', fn (t) {
      t.equal(10 + 5, 15)
      t.equal(1 + -1, 0)
    })

    tests.run
  }
}
import std.sync (Future, Promise)

class async Calculator {
  fn async fact(size: Int, promise: uni Promise[Int]) {
    let result = 1
      .to(size)
      .iter
      .reduce(1, fn (product, val) { product * val })

    promise.set(result)
  }
}

class async Main {
  fn async main {
    let calc = Calculator()

    # This calculates the factorial of 15 in the
    # background, then we wait for the result to
    # be sent back to us via a channel.
    match Future.new {
      case (future, promise) -> {
        calc.fact(15, promise)
        future.get # => 1307674368000
      }
    }
  }
}
class Stack[T] {
  let @values: Array[T]

  fn static new -> Stack[T] {
    Stack([])
  }

  fn mut push(value: T) {
    @values.push(value)
  }

  fn mut pop -> Option[T] {
    @values.pop
  }
}

class async Main {
  fn async main {
    let stack = Stack.new

    # This infers `T` to `Int`.
    stack.push(42)

    # This is an error, as `T` is inferred to `Int`, and
    # `String` isn't compatible with `Int`.
    stack.push('Oh no!')

    # The compiler can also infer earlier types based
    # on how they are used later on:
    let stack = Stack.new
    let value = stack.pop # The exact type isn't known yet

    10 + value.get # `value` inferred as `Option[Int]`
  }
}
class async Main {
  fn async main {
    let mut num = 0

    while num < 10 { num += 1 }

    loop {
      # This loops run forever. You can skip/break
      # iteration using `next` and `break`.
    }
  }
}
trait ToString {
  fn to_string -> String
}

class Person {
  let @name: String
}

impl ToString for Person {
  fn to_string -> String {
    @name
  }
}

# Traits can also be implemented conditionally:
class List[T] {
  # ...
}

# Here `ToString` is only available for instances
# of `List` if `T` also implements `ToString`.
impl ToString for List if T: ToString {
  fn to_string -> String {
    '...'
  }
}
import std.drop (Drop)
import std.stdio (Stdout)

class Thing {}

impl Drop for Thing {
  fn mut drop {
    Stdout.new.print('Thing is dropped')
  }
}

class async Main {
  fn async main {
    let thing = Thing()

    # Thing goes out of scope here, running its destructor.
  }
}
fn div(left: Int, right: Int) -> Result[Int, String] {
  # `throw x` is short for `return Result.Error(x)`,
  # saving us a bit of typing.
  if right == 0 { throw 'Attempt to divide by zero' }

  Result.Ok(left / right)
}

fn div2(left: Int, right: Int) -> Result[Int, String] {
  # `try` is short for matching against a value,
  # and throwing it again if it's an `Error`. It
  # also works for `Option` values, in which case
  # it throws a `None` if the value is also a
  # `None`.
  let res = try div(left, right)

  if res == 5 { Result.Ok(50) } else { Result.Ok(res) }
}

class async Main {
  fn async main {
    # `Result` is just an algebraic data type, so we can
    # pattern match against it to get the underlying
    # value.
    match div(10, 2) {
      case Ok(val) -> val
      case Error(err) -> panic(err)
    }

    # We can also just get the Ok value if we're
    # certain we'll never get an `Error` case:
    div(10, 2).get # => 5
  }
}
Deterministic automatic memory managementInko doesn't rely on garbage collection to manage memory. Instead, Inko relies
on single ownership and move semantics. Values start out as owned and are
dropped when they go out of scope:let numbers = [10, 20, 30]

# "numbers" is no longer in use here, so it's dropped.
return
These values can be borrowed either mutably or immutably. Inko allows multiple
borrows (both mutable and immutable borrows), and allows moving of the borrowed
values while borrows exist:let a = [10, 20, 30]

# All of this is perfectly fine:
let b = ref a # borrows "a" immutably
let c = mut a # borrows "a" mutably
let d = a     # moves "a" into "d"
This gives you the benefits of single ownership, but at a fraction of the cost
compared to languages such as Rust. The use of single ownership also means more
predictable behaviour and performance, and not having to spend a long time
adjusting different garbage collection settings.Inko is safeWith Inko you never again have to worry about NULL pointers, use-after-free
errors, unexpected runtime errors, data races, and other types of errors
commonly found in other languages. For optional data Inko provides an Option
type, which is an algebraic data type that you can pattern match against. Inko
supports both mutable and immutable references, allowing you to restrict
mutation where necessary.Concurrency made easyInko uses lightweight processes for concurrency, and its concurrency model is
inspired by Erlang and
Pony. Processes are isolated from each other and
communicate by sending messages. Processes and messages are defined as classes
and methods, and the compiler type-checks these to ensure correctness.The compiler ensures that data sent between processes is unique, meaning there
are no outside references to the data. This removes the need for (deep) copying
data, and makes data races impossible. Inko also supports multi-producer
multi-consumer channels, allowing processes to communicate with each other
without needing explicit references to each other.Here's how you'd implement a simple concurrent counter:import std.sync (Future, Promise)

class async Counter {
  let @value: Int

  fn async mut increment {
    @value += 1
  }

  fn async get(promise: uni Promise[Int]) {
    promise.set(@value)
  }
}

class async Main {
  fn async main {
    let counter = Counter(value: 0)

    counter.increment
    counter.increment

    match Future.new {
      case (future, promise) -> {
        counter.get(promise)
        future.get # => 2
      }
    }
  }
}
Error handling done rightInko uses a form of error handling inspired by Joe Duffy's excellent article
"The Error Model". Errors
are represented using the algebraic type "Result", and Inko provides syntax
sugar in the form of try and throw to make error handling easy. Critical
errors that can't/shouldn't be handled are supported in the form of "panics",
which abort the program when they occur.For example, here's how you'd handle errors when opening a file and calculating
its size:import std.fs.file (ReadOnlyFile)
import std.stdio (Stdout)

class async Main {
  fn async main {
    let size =
      ReadOnlyFile
        .new('README.md'.to_path)     # => Result[ReadOnlyFile, Error]
        .then fn (file) { file.size } # => Result[Int, Error]
        .or(0)                        # => Int

    Stdout.new.print(size.to_string) # => 1099
  }
}
EfficientInko aims to be an efficient language, though it doesn't aim to compete with
low-level languages such as C and Rust. Instead, we aim to provide a compelling
alternative to the likes of Ruby, Erlang, and Go.Inko uses a native code compiler, using LLVM as its
backend, and aims to provide a balance between fast compile times and good
runtime performance. The native code is statically linked against a small
runtime library written in Rust, which takes care of scheduling processes,
non-blocking IO, and provides various low-level functions.Pattern matchingInko supports pattern matching on a variety of types, such as tuples and
algebraic data types:match [10, 20].opt(1) {
  case Some(number) -> number # => 20
  case None -> 0
}

match (10, 'hello') {
  case (10, 'hello') -> 'foo'
  case (20, _) -> 'bar'
  case _ -> 'baz'
}
You can also match against literals such as integers and strings, and against
regular classes:class Person {
  let @name: String
  let @age: Int
}

let alice = Person(name: 'Alice', age: 42)

match alice {
  case { @name = name } -> name # => 'Alice'
}
Pattern matching is compiled down to decision trees, and the compiler tries to
keep their sizes as small as possible. The compiler also ensures that all
patterns are covered.