> Inko 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.
Now I'm very interested. I was wondering why with 'actors' we'd still mark functions as async. It seems that a class marked async is analogous to an actor.
class async Counter {
let @value: Int
fn async mut increment {
@value += 1
}
fn async send_to(channel: Channel[Int]) {
channel.send(@value)
}
}
A class marked as "async" can still have regular methods, which are only
available to the inside of the class (i.e. the process itself, not other
processes sending messages to it). So you can do something like this:
class async SomeProcess {
fn async some_message(...) {
...
some_helper_method
}
fn some_helper_method {
...
}
}
SomeProcess {}.some_message # This is fine
SomeProcess {}.some_message # This is a compile-time error
I toyed with the idea of _not_ allowing regular method and thus implicitly
making all of them async for an async class, but that might lead to a pattern of
async classes being "mirrored" by non-async "helper" classes just to reuse some
methods between the different messages of an async class, i.e. you end up with
this:
class async SomeProcess {
fn some_message(...) {
SomeProcessHelper {}.some_random_method
}
}
class SomeProcessHelper {
fn some_random_method {
...
}
}
Glad to see you here. Thanks that all makes sense, as making the async class's method async by default would then needs another keyword to make one non-async.
Is the mut marking also for consistency (and/or non-message methods)? Presumably each actor/object would only process one message at a time so could always have full access to all its data.
One more question, is memory allocated globally or from arenas/slabs/etc?
> Is the mut marking also for consistency (and/or non-message methods)? Presumably each actor/object would only process one message at a time so could always have full access to all its data.
This is indeed correct: processes have exclusive access to their data and run a single message at a time. Still having to tag methods as mutable is to prevent you from accidentally mutating data, and to keep it consistent with regular methods.
This is all an amazing achievement, congrats on making it so far/long.
I always felt that Rust wasn't a local maxima and there was something that could be added/removed. The only thing I'm not sold on is async (outside of actor use) vs futures where waiting explicitly blocks the waiter, but can be composed without waits. There seems to be some debate around Rust's choice of implementation.
> 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.
Now I'm very interested. I was wondering why with 'actors' we'd still mark functions as async. It seems that a class marked async is analogous to an actor.