# IoLanguage/io: the prototype-based Io language on WebAssembly

> Io is a dynamic prototype-based language in the Smalltalk and Self family, and the current master branch targets WebAssembly through wasi-sdk rather than a native CMake build. Here is what the repository documents, how to build and run it, and where it stops being the right tool.

**IoLanguage/io** — Io programming language. Inspired by Self, Smalltalk and LISP.

- Repository: https://github.com/IoLanguage/io
- Website: http://iolanguage.org
- Stars: 2,791 · Forks: 309
- Language: C
- License: NOASSERTION
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/iolanguage-io

## What Io is, and the problem a prototype-based language solves

Io is a dynamic prototype-based programming language in the same family as Smalltalk and Self. The README's own framing is blunt: everything is a message send from one object to another. There are no classes in the sense a Java or C# reader expects. You clone an existing object and add slots to the clone, which is how the README's Contact example works: Contact := Object clone, then Contact name ::= nil, and later Contact fullAddress := method(list(name, address, city) join("\n")).

The audience is narrow and specific. This is for people who want to study or build on a live object model where inheritance is delegation, and for engineers evaluating prototype-based designs without the ceremony of a class keyword. It is also for anyone who wants to embed a small dynamic language in a WebAssembly host, since the current branch ships a WASI binary and a bidirectional Io to JavaScript bridge. It is not a general-purpose application language with a large ecosystem of libraries; the repository is the language, its libraries and its samples.

## Messages all the way down: how the object model actually works

The mechanism visible in the README is uniform. A number is an object, so 2 sqrt and 1 + 1 are message sends, not special syntax. A list is an object, so d := List clone append(30, 10, 5, 20) creates one and d select (>10) filters it by sending select with a block argument. The block >10 is itself an object that receives a value.

Objects are cloned rather than instantiated. Contact := Object clone produces a new object whose printed form shows its type slot, and the ::= operator creates a slot on that object. When holmes := Contact clone setName("Holmes") setAddress("221B Baker St") setCity("London") runs, the clone carries the same slots and the setters return the object so calls can be chained. Methods are slots holding methods: Contact fullAddress := method(...) attaches a method to the Contact object, and holmes fullAddress resolves it by walking up from the clone.

Underneath, the repository is a C codebase. The Makefile compiles libs/basekit/source, libs/garbagecollector/source and libs/iovm/source, plus deps/parson for JSON and deps/libtommath for arithmetic, against the wasi-sdk sysroot. There is a garbage collector library in the tree, and a make regenerate target that regenerates IoVMInit.c from .io sources, which is how the language's own bootstrap code becomes C that the compiler can build.

## Building Io with wasi-sdk and running your first script under wasmtime

The README lists two prerequisites: wasi-sdk and wasmtime. The build is a recursive clone followed by make. The recursive flag matters because deps/ is populated through git submodules.

```bash
git clone --recursive https://github.com/IoLanguage/io.git
cd io
make
```

If wasi-sdk is not at ~/wasi-sdk, the Makefile exposes WASI_SDK as an overridable variable. Point it at your install directory and the cross-compiler path is rebuilt from it.

```bash
make WASI_SDK=/opt/wasi-sdk
```

The default target produces build/bin/io_static, a WASI CLI binary. You can run a one-liner through wasmtime without writing a file.

```bash
wasmtime build/bin/io_static -e '"hello" println'
```

Expect hello on stdout. To run a script from disk, the README notes you must pass --dir=. so the WASI sandbox can read the file. That is not optional politeness; without the preopened directory the runtime cannot see your script.

```bash
wasmtime --dir=. build/bin/io_static path/to/script.io
```

Running wasmtime build/bin/io_static with no arguments starts the REPL. From there, the README's examples work directly: type 1 + 1 and you get 2, type d := List clone append(30, 10, 5, 20) and you get list(30, 10, 5, 20).

For the browser path, make serve starts a server on http://localhost:8000 and make browser builds browser/io_browser.wasm. The README points at browser/README.md for embedding the REPL in a page and calling between Io and JavaScript.

## The WebAssembly move costs you the native addon system

This is the limitation that will decide adoption for most teams. The README states plainly that the native addon system, DynLib, AddonLoader and the Eerie package manager are not available on WebAssembly. Host integration goes through the JavaScript bridge instead, and the README points to browser/README.md and agents/wasm/Bridge.md for that.

So if your plan involves loading a compiled C addon at runtime, or pulling a package through Eerie, master is the wrong branch. The README directs you to the native branch or the 2026.04.20-native-final tag, and adds that the native path accepts bug fixes while new development lands on master. That is a split worth reading carefully: the branch with the addon system is the one that is no longer the focus.

There is a second constraint in the WASI sandbox itself. The README's script example requires --dir=. because file access is preopened, not ambient. Any tool that expects to read arbitrary paths, spawn processes or use signals will meet the WASI emulation layer, and the Makefile's flags show which pieces are emulated: -D_WASI_EMULATED_PROCESS_CLOCKS and -D_WASI_EMULATED_SIGNAL, linked with -lwasi-emulated-process-clocks and -lwasi-emulated-signal. Emulated is the operative word. If your workload depends on real process control, this is not the target for it.

## Io against Janet and Self: same family, different centre of gravity

The related searches around this project include Janet and Self, and the comparison is instructive. Self is the direct ancestor of the object model here: prototypes, cloning, delegation. The difference is packaging. Self was a research system with its own environment; Io is a C language implementation you build with make and run as a binary, and on master that binary is a WASI module rather than a native executable.

Janet is the closer practical alternative for someone who wants a small embeddable dynamic language today. Janet is also a Lisp-flavoured, embeddable language with a C core, but its centre of gravity is native embedding: you compile it into a host program and call into it. Io's current centre of gravity is the opposite direction. The README describes a single WASI binary that runs under any WASI host, wasmtime, Node, or browsers via WASI shims, plus a browser REPL and an Io to JavaScript bridge. If your host is JavaScript or a WASM runtime, Io's bridge is the documented integration path. If your host is a native C program that wants to load addons, Janet's model matches that shape more directly, and Io's native branch is the fallback rather than the default.

The object models also differ in feel. Janet is Lisp-like with a strong functional streak. Io is message-send all the way down, closer to Smalltalk, and the README's examples read as a sequence of unary and keyword messages rather than s-expressions.

## Tests, releases and what maintenance looks like from the outside

The repository has two test suites and a target that runs both. make test builds build/bin/test_iterative_eval, and make check runs both suites under wasmtime. You can also run them individually.

```bash
wasmtime build/bin/test_iterative_eval
wasmtime --dir=. --dir=/tmp build/bin/io_static libs/iovm/tests/correctness/run.io
```

Setting IO_TEST_VERBOSE=1 gives per-test output, which is the switch to reach for when a failure is not self-explanatory. The browser path has its own target, make check-browser, which the Makefile comment says runs browser tests under Playwright.

On releases, the most recent one listed is 2019.05.22-alpha, dated 2019-05-21. That is a long gap between tagged releases and the work described in the README, which talks about a WebAssembly target, a browser REPL and a JavaScript bridge that the 2019 tag predates. The practical reading is that the tag does not describe master. Anyone pinning to a release for stability should check what that tag actually contains before assuming it matches the documented build.

The last push to the repository was on 2026-09-01, so the codebase is being touched. That is a statement about commit activity, not a promise about API stability, and the README's own note that new development lands on master while the native path only accepts bug fixes is the clearest signal about where change will appear.

## Licence and the cost of keeping a language build alive

The repository's licence is reported as NOASSERTION, which means the automated classification could not map LICENSE.txt to a known SPDX identifier. The file is present at the top level. If you plan to redistribute Io, or to ship a binary built from it, read LICENSE.txt yourself and get your own answer; this article cannot tell you what terms it sets.

The upgrade cost is dominated by the toolchain rather than the language. Building requires wasi-sdk and wasmtime, and the Makefile pins the cross-compiler to $(WASI_SDK)/bin/clang with an explicit sysroot. When wasi-sdk moves, the flags in CFLAGS are what you will be adjusting: the emulated clocks and signals defines, the sysroot path, and the include directories for basekit, the garbage collector, iovm, parson and libtommath. There is also a host compiler step, HOST_CC := cc, used for the io2c code generator, so a machine needs both a native C compiler and the WASI cross-compiler.

Because the build is a recursive clone, deps/ arrives as submodules. Updating the language means updating those submodules in step, and make regenerate exists because IoVMInit.c is generated from .io sources rather than edited by hand. Anyone who edits generated C directly will lose the change on the next regenerate.

## Conclusion

Adopt IoLanguage/io if you want to read or extend a small prototype-based language whose current build target is a single WASI binary, and if you are comfortable with a make-driven toolchain that expects wasi-sdk and wasmtime on the machine. Do not adopt it if you need the native addon system, DynLib, AddonLoader or Eerie, because the README states those are not available on WebAssembly; that path lives on the native branch or the 2026.04.20-native-final tag. Before committing, verify that wasi-sdk sits at ~/wasi-sdk or that you can pass WASI_SDK, confirm wasmtime is installed, and run make check to see both suites pass on your host.

## FAQ

### How do I install IoLanguage/io?

Clone the repository recursively so the submodules in deps/ are populated, then run make. The README lists wasi-sdk and wasmtime as prerequisites, and if wasi-sdk is not at ~/wasi-sdk you pass WASI_SDK to make to point at your install.

### Can I run IoLanguage/io in a browser?

Yes. The README documents make serve, which serves the browser REPL at http://localhost:8000, and make browser, which builds browser/io_browser.wasm. Details on embedding the REPL and calling between Io and JavaScript are in browser/README.md.

### Why does running an Io script under wasmtime fail to find the file?

The README's script example passes --dir=. to wasmtime, because the WASI sandbox only sees directories you preopen. Without that flag the runtime has no access to the script path.

### Does IoLanguage/io still support native addons and Eerie?

Not on master. The README states the native addon system, DynLib, AddonLoader and Eerie are not available on WebAssembly, and directs you to the native branch or the 2026.04.20-native-final tag for the previous native build.

## Sources

- [IoLanguage/io on GitHub](https://github.com/IoLanguage/io)
- [Issues](https://github.com/IoLanguage/io/issues)
- [Project website](http://iolanguage.org)
- [README](https://github.com/IoLanguage/io/blob/master/README.md)
- [Releases](https://github.com/IoLanguage/io/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/iolanguage-io
