Why I'm Excited About Inko
But I’ve really been looking for something a little more native. It seems to me that there is a big gap on the language spectrum, somewhere between Rust and Go:
Don’t read too much into this little diagram. Roughly, on the left we have languages that are low-level, perhaps harder to learn, and statically ahead-of-time-compiled. On the right, we have highly dynamic languages that run in some sort of virtual machine.
This is, by no means, the only way these languages could be charted. For example, Rust is by far the hardest to learn of all of these languages, but I didn’t put it as far to the left as C and Zig because it has much more robust memory management support. I left a bunch of popular languages off on the right side, partially because I was running out of room and colours, but mostly because I was losing interest in drawing pictures.
The point is that there seems to be a gap in the diagram. I’m looking for something that is at least as easy to use as Go (preferably easier), but that does more work at compile time (ie: does not have a garbage collector.) It doesn’t have to do all the work at compile time; indeed, I think Rust’s emphasis on zero-cost abstractions has cost the language dearly – it’s not truly “zero” cost if it slows down your compile-times or makes the language harder to learn. Those are costs. Big ones.
As you might guess from the title of this article, I think Inko fills this gap beautifully. There are numerous other languages that are aspirationally trying to slot into the “easy to use but without a garbage collector or virtual machine” gap. I’ve studied a lot of them and Inko has quickly become my favourite.
So What Is Inko?
Inko is an ahead-of-time compiled, statically and strongly typed, slightly object-oriented language with a unique memory management system and actor-based concurrency model.
As a comparison to other languages, I think of Inko as:
- Go without the garbage collector
- Rust without the borrow checker
- Python with true parallelism
- Java without inheritance
- Pony, except practical
- C++ without the foot guns
I think Inko has the potential to steal users from any of those languages. It doesn’t satisfy every possible use case out there, but I have a sense that that the vast majority of server-side and systems programming projects would be more enjoyable to code in Inko than many other languages. Someday. (I’ll go into the drawbacks later.)
It is my considered opinion that developer velocity is the chief deciding factor in the eventual success of a coding project. If you can code twice as fast, you can afford to get it wrong twice as often.
This is one reason I love Python: It compiles to bytecode so fast that most people have forgotten that a compiler exists at all. Unfortunately, though, Python’s pathetically slow test frameworks eat into these gains quickly.
Developer velocity is also a reason I love Rescript. It compiles and runs all your unit tests in less time than it takes the Rust compiler to decide it has to build something. I don’t like having to wait entire minutes for relatively simple code to tell me it isn’t working.
When I started using Inko a couple months ago, it compiled to byte code as fast as Python does and the unit tests ran instantaneously. It was heavenly. Unfortunately, my experiences with Python and Node systems have me desiring the simplicity of Go’s statically linked binaries, so I wasn’t too keen on “yet another language that ships with a virtual machine.” It’s been done to death (see all the languages I neglected to include on the right side of the diagram above).
However, as of a couple weeks ago, Inko’s developer finished a massive refactor to do away with bytecode and virtual machine. It now compiles directly to native code using llvm. Sadly, this reduced the compile times from “instant” to “pretty quick,” but it is still “fast enough” that I’m delighted to work with it. I truly hate messing around with docker or virtual environments, and Inko’s emulation of Rust’s “if it’s installed, it works” experience is totally worth it. And after conversations with Inko’s developer, I am confident there will be compiler optimizations coming in the future to make compile times even faster.
Concise and Consistent
Inko is a surprisingly small language with relatively few concepts, though some of those concepts are rather unusual. Every piece of syntax feels like it was intentionally crafted to fit in with the language’s overall ethos. This is similar to Rescript, Gleam, and Lua, and very different from Typescript, PERL and Rust, which all seemed to be designed along the lines of “here’s a new problem to solve, what random syntax can we add to fix it?”
In my opinion proper language design should instead aspire to “what can we take away to fix it?”
Of course, part of Inko’s brevity is due to it being a very young language. I’m sure that it will grow and evolve over time. Python is over 30 years old, and in spite of the streamlining that happened at the Python 3 transition, it still carries quite a bit of dead weight around with it. I can only hope that Inko is dealing with dead weight in 30 years. That means it’s been phenomenally successful.
One example that originally struck me as very odd, but made sense after I dug deeper into the language is the
for loop: At first glance, Inko doesn’t have a built-in construct for iteration. You can use the
which is actually the only way to loop in Inko and the only way to exit a loop is to explicitly
return from it.
But more often, iteration involves using a functional style (with a large collection of iterating tools in the standard library)
combined with Inko’s (much more readable than average languages) closure syntax. Instead of adding a new language construct for iteration,
Inko focused on making existing syntax for closures enjoyable to use.
Another example is the recent demise of Inko’s exception syntax: Inko 0.10 had an interesting exception syntax that basically
encoded two different return types in the function signature. With the recent release of 0.11, Inko completely removed
that syntax and now uses a Rust-style
Result algebraic datatype instead. It kept some of the syntactic sugar to make it easy to work
Results, but fundamentally, the whole concept of “exceptions” got reduced to the pre-existing
Opinionated Memory Management
Inko uses a memory management scheme that I’ve never seen anywhere else. Like Rust, C++, and C and unlike Go, Inko does not ship with a garbage collector. Like Rust, Inko’s memory management is “safe” in the sense that you cannot access null pointers or create use-after free errors.
Unlike Rust, Inko doesn’t catch all possible memory management issues at compile time. Instead, it uses a small amount of memory to catch theese errors at runtime. This is an interesting tradeoff. Compared to a reference counted and garbage collected language, Inko uses much less memory and CPU cycles. Instead of targetting so-called “zero-cost abstractions” Inko’s position is that a bit of runtime overhead is a worthwhile cost for a language that is much simpler to develop in.
The memory management system is one area where I’m still not 100% confident that Inko has made the right decision.
Indeed, memory management in Inko reduces to “Single Ownership is All You Need.” Objects in Inko are freed when the “owner” goes
out of scope. This is quite similar to the generally accepted “normal” behaviour in Rust or C++, but unlike those languages,
Inko doesn’t also ship with a reference counted alternative (Rust’s
Arc or C++’s
Inko compensates for this by allowing you to use as many normal references (mutable or immutable) as you like. This means that data structures such as trees that often use reference counting can be solved using standard references, as long as you somehow guarantee that an owner exists.
In my experiments, I’ve been able to solve this either by being hyper-vigilant about recognizing who owns an object, or (rarely) by creating a sort of pseudo-arena array that owns the values, and then use references to values in that array everywhere.
The unusual thing that Inko does is throwing a runtime error when you free an object if references to that object still exist. This means you have to be keenly aware of when stuff goes out of scope. If you’re used to reference counted and/or garbage collected languages, this awareness can feel like a lot of overhead. On the other hand, you don’t have to be quite as aware of it as you do when coding C or C++ because the runtime errors help you find issues that would simply be blatant security holes in those languages. Further, identifying and fixing memory related bugs in Inko is far more pleasant than in Rust because you don’t have to “fight the borrow checker” as the saying goes.
For the most part, I think Python is the optimal language, but Python’s achilles heel is trying to handle parallel workloads. Sure there are numerous ways to do it, but they’re all kind of hacky. I’ve spent a substantial portion of my career pushing Python beyond its limits, and I get quite agitated when people say “Python is slow.” It’s really not if you know how to work with it.
So when I’m looking at that gap in the diagram above, I really want something that is “almost as easy and enjoyable to code as Python, but better at parallel workloads.” To be fair, memory management in Inko is NOT “almost as easy” as Python, but I do find it enjoyable, and I’m willing to accept the added overhead for the concurrency primitives.
Much like it’s approach to memory management, Inko is opinionated about concurrency: There is only one way to do concurrency in Inko, but it is the way that I think concurrency should (almost) always be done: share nothing and pass messages.
Like Go and Pony, Inko ships with a “thick runtime” that handles concurrency for you. You can create
async classes that Inko calls processes
that manage their
own state independently of other processes. The only way to share state between these “processes”
is to ensure that the state is a unique pointer (has no references at all) before passing ownership to the other process. This is
sufficient to guarantee that you have no race conditions or deadlocks.
Inko’s system for this is very similar to Pony, and both are inspired by the same research paper. Indeed, when I first discovered Inko, I was specifically looking for a language that was “like Pony, but more practical.” Pony is a beautiful language to read about, but I find it too academically focused on “purity” rather than “practicality” when I actually try to write anything complex in it.
I recently completed a rudimentary Http server in Inko, just to explore the concurrency. It’s nowhere near production-ready, but it was a fascinating exercise to learn how to work with Inko’s concurrency primitives, and it was sufficient to convince me that the concepts are sound.
Big Enough Standard Library
Inko ships with a standard collection of… well, collections. It is by no means “batteries included” like Python, though in my opinion, the batteries included idea was far more valuable way back in the days when Python was used by people who might not have access to the Internet.
Inko’s standard library is enough to handle the usual collections (arrays, maps, and sets), manipulating primitives, string processing, filesystem access, and networking. With one glaring exception (regular expressions), I found that it was adequate for my experiments.
One thing that is currently missing from Inko is foreign function interfaces. You can’t call out to native code from Inko at all, right now. This is actually a recent regression; Inko 0.10 supported a basic FFI API, but it was scrapped to make room for the native compiler. We can expect FFI to come back to Inko in the future; this is obviously a prerequisite for considering Inko a “real” programming language.
Surprisingly Performant (considering it’s completely unoptimized)
I haven’t done any benchmarks, but I’ve found Inko to build code that is fast enough. The compiler doesn’t currently do much in the way of optimizations (though it does compile to llvm, which introduces its own optimizations), but code still runs quickly.
I had considered doing some benchmarks to compare Inko to Rust, Pony, and Go more fairly, but to be honest… I get bored writing benchmarks and I always mess up the methodology. So hopefuly someone reading this who finds such things interesting will be up for creating a fair comparison.
One of the things that I love about Inko is how well organized the complexity is. As someone who writes programming books, let me tell you that figuring out the order to explain concepts to readers is absolutely the hardest part. (You know a book is poorly organized if it constantly says, “we’ll discuss XXX in Chapter #, for now just know that…”).
If I ever write a book on Inko, I feel like I already implicitly know what order to introduce concepts in. You can do a lot in Inko without having any references at all. Contrast this with Rust where lifetime elision sort of allows a newcomer to not think about the borrow checker for a short time, but in practice that specter is going to rear its head in a hurry.
And when you do need to learn about references in Inko, they’re pretty straightforward. You have to know that they can be either mutable or immutable, but there are no arcane rules about how many of each you can have to an object under what circumstances.
The most complicated aspect of inko is
uni values; the unique pointers (akin to Pony’s isolated type) values that
are required to pass data to other processes. But you don’t need to know anything about the existence of uni values until
you actually want to do concurrency in Inko, and that can be a relatively late concept.
This facet is, a) completely subjective, and b) in my opinion the most important feature of a programming language. I love writing software. I’ve loved doing it for well over half my life. But the simple truth is, I love doing it in some languages more than in others. This is all about personal preference; I’m the kind of person who greatly prefers coding in Python to coding in Ruby, even though the two languages are really very similar. For whatever reason, Ruby just kinda gets on my nerves, and Python just kinda gets out of my way. But that is specific to me; some people prefer Ruby’s style over Python’s and that’s totally fine.
So, IN MY OPINION, Inko is one of the more enjoyable languages I’ve worked with. I can’t put my finger on why, but it’s a combination of all the things above and more; the general vibe that the language gives off seems to suit my personality. For comparison, other languages that I really enjoy working in include Python and Rescript.
On the other hand (also IN MY OPINION), Inko is far more entertaining to code with than C++, Rust, Go, or Java; languages you will notice on either side of the gap in the diagram that Inko fills. I find it to be–by far–the most enjoyable of any non-garbage-collected languages I know. Coding in Inko is more frustrating than coding in Rescript or Python, for sure, but those languages are pretty far away from it on my little map.
Another reason I’m excited for Inko is that the creator and developer, Yorick Peterse, is currently developing the language full time. Most of the other esoteric languages I’ve looked at are either hobby projects or academic projects that people are working on in their limited free time or not at all. I’m not sure how Yorick’s managing this financially, but I want him to keep it up so I’m contributing a wee bit to his Github sponsorship and hope this article will convince a few other people to do so as well.
For example, one other language that I’ve been watching closely is Vale. Vale is aspirationally attempting some very interesting things with memory management, but development is pretty slow. Seeing Inko quickly evolve day by day has been comparatively much more interesting (I recently started building with the main branch of the compiler instead of the latest release).
Inko doesn’t have much of a community and therefore doesn’t have much of a culture, yet. That said, Yorick seems like a great human being to be leading a project. He documents things thoroughly, is extremely open to the dumbest questions (even when I ask them more than once), and very thoughtful about how a language should be designed. Not only is he crafting a language that I want to use, he’s setting an example that I think will create to a terrific language community that I want to be part of in the long run.
But What Can’t Inko Do?
There are a few things that I think Inko is inherently unsuited for, and few more that it is currently not capable of but will change as the language matures.
First, Inko is more like Go than Rust in that it ships with a thick runtime that is great for a wide array of problems, but is not suitable for certain very low-level things such as operating systems. In the old days, folks would say it was unsuitable for embedded systems, but embedded systems ship with a lot more memory than supercomputers did back when people were saying such things. Indeed, nowadays Python is often used on embedded systems. I’m not even sure if the concept of “embedded system” still exists.
Inko is absolutely unsuitable for problems that rely on shared memory for concurrency. Though I’m not clear if there are legitimate areas where shared memory is “better” than message passing, and I’m pretty confident there are very few problems for which shared memory is the only solution.
There may be certain data structures that would be obscenely hard to implement in Inko due to its opinionated reliance on single ownership, though I’ve struggling to come up with any examples. That said, many data structures that are notoriously difficult to work with in Rust (e.g. trees, graphs), are not bad in Inko because it has fewer restrictions on references.
Inko doesn’t currently compile to WASM and I suspect it would be pretty hard to make it do so because WASM doesn’t really provide the concurrency primitives that Inko would need to rely on. Similarly, I can’t really see Inko being used for mobile development any time soon.
Those are the only things I can think of that Inko is likely not capable of handling, but there is a whole bunch of stuff that Inko can’t do right now, not because of any inherent problems with the language design, but because those features simply haven’t been implemented.
At the top of the list is “any production system”. Inko is a very young language. I’ve encountered numerous compiler bugs, though they haven’t stopped me from being reasonably productive (and I think they’ve all been fixed already). I have no intention in the near future of using Inko for business purposes or anything that I expect to remotely resemble a production environment. This is partially because the compiler is too young to trust, and partially because the language is evolving at a rapid clip and I don’t want the headache of trying to keep a production system up to date with the latest syntax changes.
Second is foreign function interfaces. You can’t currently call third-party libraries written in other languages. This means Inko can’t access the vast array of C or Rust libraries out in the world, which makes it effectively useless. As I mentioned before, this is a temporary situation and Inko actually used to support FFI; it’s just been temporarily disabled.
Relatedly, the third thing is third party libraries. If you can’t do something using FFI, you necessarily have to do it using Inko code, right? Well, to the best of my knowledge, there are no third party packages for Inko yet, other than the http framework I wrote. Inko has only shipped with a package manager for a couple of weeks, and there isn’t much community activity. So if you want to do something that isn’t covered by the standard library, you have to write it yourself, from scratch. But… in a way, that is kind of fun! Indeed, my next project is probably going to be a regex engine in Inko.
So, where should I use it?
On toy server-side projects that you are doing 100% for the fun of it with no intention of turning into anything of value. This happens to include almost all of my “free time coding”, but I know I’m weird. If you’re bored with Python, or Go, or Rust, Inko might be a fun language to play around with for a while. Who knows, maybe you’ll make a new framework that will become the language’s first killer app!
I’ll be writing some tutorials on Inko in the future because that’s what I like to do when I’m learning a new language or framework!