Limbo
Bell Labs' concurrent systems language for the Inferno operating system - a C-flavored, garbage-collected language with typed channels, abstract data types, and dynamically loaded modules, compiled to the portable Dis virtual machine, that later fed directly into the design of Go
Created by Sean Dorward, Phil Winterbottom, and Rob Pike at Bell Labs (Computing Sciences Research Center); Dennis Ritchie wrote the language description
Limbo is the application programming language of Inferno, the distributed operating system that Bell Labs built in the mid-1990s as a successor-in-spirit to Plan 9. Designed by Sean Dorward, Phil Winterbottom, and Rob Pike, and described in a language report written by Dennis Ritchie, Limbo is a compact, C-like language with garbage collection, strong static typing, abstract data types, first-class communication channels, and cheap lightweight processes. Limbo programs compile to bytecode for the Dis virtual machine, which lets the same binary run on any Inferno host or native port. It never became a mainstream language, but its concurrency model - spawn, typed chans, and alt - was carried almost intact into Go, which makes Limbo one of the more consequential “dormant” languages in the systems world.
History & Origins
Bell Labs after Plan 9
By 1995 the Computing Sciences Research Center at Bell Labs had spent nearly a decade on Plan 9, the research operating system that generalized the Unix “everything is a file” idea into a network protocol (9P) and a per-process namespace. Plan 9’s application language was Alef, a C-like language by Phil Winterbottom that added abstract data types and CSP-style channels. Alef was compiled to native code and had to be ported, compiler and all, to every architecture Plan 9 supported.
The Inferno project began in 1995 with a different target: small, networked consumer devices - set-top boxes, “intelligent” telephones, PDAs - with limited memory and no fixed architecture. The team (Dorward, Winterbottom, and Pike, with Dave Presotto, Howard Trickey, and Dennis Ritchie also on the Inferno papers) wanted the Plan 9 model of files and namespaces, but with a language that was safe, garbage collected, and portable across hardware without recompilation. Limbo was the answer. Its name, like Inferno’s, comes from Dante; the virtual machine is Dis, the city of the lower Inferno, and the file protocol is Styx.
Java’s competitor
Dennis Ritchie reportedly let the existence of the project slip in early 1996, after less than a year of development, and Lucent Technologies - freshly spun out of AT&T - presented Inferno publicly later that year as a direct competitor to Java for networked devices. The comparison was apt: both were garbage-collected, virtual-machine-based, and aimed at portable code for a networked world. Inferno 1.0 shipped in 1997, and Lucent set up a dedicated Inferno Business Unit to sell it.
The commercial push did not last. Lucent used Inferno in two of its own products - the PathStar access server and the VPN Firewall Brick - but the business unit closed in 2000, and in March of that year Lucent sold the rights to Vita Nuova Holdings, a small company in York, England, with close ties to the Plan 9 and Inferno research community. Vita Nuova released the 3rd Edition in 2001 and, beginning with a preliminary public release in May 2004, moved the 4th Edition to free-software terms. In March 2021 all editions were relicensed under the MIT License, and community forks such as Inferno64 have since carried the system to 64-bit hosts.
Design Philosophy
Ritchie’s language report summarizes Limbo’s ancestry in one sentence: expression syntax and control flow from C, declarations from Pascal, abstract data types and channels from Alef, and processes from Hoare’s CSP and Pike’s Newsqueak. Around that core, several principles shaped the language:
- Safety without a native compiler. Limbo has no pointer arithmetic, no unchecked casts, and no unsafe memory access. Type safety is verified at compile time and enforced at run time by the Dis VM, which lets untrusted Limbo modules be loaded into a running system.
- Portability as a first-class goal. Limbo compiles to Dis bytecode, not machine code. The Dis instruction set was designed to be translated to native code cheaply at load time, so a single
.disfile runs on any architecture with a Dis port - x86, ARM, MIPS, and PowerPC among others, according to the Inferno documentation. - Concurrency as a language feature. Processes are created with
spawn, communicate over typed channels, and multiplex withalt. Shared memory is possible but the idiom is message passing. - Modules as the unit of everything. Programs are collections of modules with explicit interfaces. Modules are loaded dynamically by path name, so the file system - and therefore the network, via Styx - is the module namespace.
- Small enough for appliances. The whole runtime, including the VM, garbage collector, and graphics, was meant to fit in a device with roughly a megabyte of memory, according to the original Inferno papers.
Key Features
Types and declarations
Limbo’s basic types are byte, int (32-bit), big (64-bit), real (64-bit floating point), and string. Compound types include fixed-size array of T, singly linked list of T, tuples such as (int, string), chan of T, ref to an adt, and function types. Declarations follow the Pascal convention of name first:
count: int;
names: list of string;
grid := array[10] of { * => 0 }; # := declares and initializes
pair := (3, "three");
Strings are immutable Unicode sequences with built-in concatenation and slicing; arrays and lists are garbage collected, as are adt instances allocated with ref.
Abstract data types
An adt groups data members with the functions that operate on them, giving encapsulation without inheritance:
Point: adt {
x, y: int;
add: fn(p: self Point, q: Point): Point;
eq: fn(p: self Point, q: Point): int;
};
Point.add(p: self Point, q: Point): Point
{
return (p.x + q.x, p.y + q.y);
}
A pick adt is a tagged union - a discriminated variant whose branches are checked by a pick statement, roughly comparable to a sum type in ML-family languages.
Modules
Every Limbo program is a module with a declared interface. The famous minimal program shows the shape:
implement Hello;
include "sys.m";
include "draw.m";
sys: Sys;
Hello: module
{
init: fn(ctxt: ref Draw->Context, argv: list of string);
};
init(nil: ref Draw->Context, nil: list of string)
{
sys = load Sys Sys->PATH;
sys->print("Hello, World!\n");
}
include pulls in the interface declaration for the system module; load fetches the implementation from the file system at run time (Sys->PATH is the $Sys built-in, but application modules are loaded from ordinary .dis files by path). Because loading happens through the namespace, a module can just as easily come from a remote Styx server as from local storage.
Channels, spawn, and alt
The concurrency primitives are what most people remember Limbo for. spawn starts a new Dis process running a function call; chan of T declares a typed channel; <- sends and receives; and alt waits on several channels at once:
c := chan of int;
done := chan of string;
spawn producer(c);
alt {
n := <-c =>
sys->print("got %d\n", n);
s := <-done =>
sys->print("finished: %s\n", s);
}
Channels can be buffered (chan[8] of int), sent over other channels, and even exported to the file system so that other machines can talk to a Limbo process over Styx. Dis processes are designed to be far cheaper than host threads (they are scheduled by the VM rather than the host OS), so Limbo programs routinely use many of them - the same style Go later called goroutines.
Exceptions and the runtime
Limbo has an exception mechanism (raise and exception blocks), reportedly added in later editions, module-level garbage collection using a hybrid of reference counting and a real-time collector for cyclic data, and a built-in Sys module that exposes the Inferno system calls - file operations, bind and mount for namespace manipulation, and Styx - directly to programs. Graphics come through the Draw and Tk modules; the window manager, editors, and browser in Inferno are all Limbo programs using them.
Evolution
| Period | Steward | Development |
|---|---|---|
| 1995-1997 | Bell Labs / Lucent | Language designed; Inferno 1.0 and the Ritchie language report |
| 1997-2000 | Lucent Inferno Business Unit | 2nd Edition (1999); commercial licensing; PathStar and Firewall Brick |
| 2000-2004 | Vita Nuova (proprietary) | 3rd Edition (2001); commercial and subscription licensing |
| 2004-2021 | Vita Nuova (open source) | 4th Edition with 9P2000 support; mixed GPL/LGPL/LPL/MIT licensing; Ritchie’s language report revised for the 4th Edition |
| 2021-present | Community | MIT relicensing (March 2021); Inferno64 fork (2022); ports to Raspberry Pi, Android, and other hosts |
The language itself changed little across editions. The most visible additions were exception handling, the pick adt, and refinements to the module system; the 4th Edition’s interface changes were mostly in the system modules rather than the language. Most of the activity in the last two decades has been in keeping the hosted emulator building on modern operating systems and widening the Dis VM to 64 bits.
Current Relevance
Limbo is dormant rather than dead. The Inferno source tree, including the limbo compiler and the emu hosted environment, is maintained on GitHub under the MIT License and is reported to build on Linux, macOS, FreeBSD, and other hosts; the repository ships a Dockerfile that builds a 32-bit hosted Inferno and launches its window manager. Forks such as Inferno64 and 9ferno keep it running on current 64-bit machines, and hobbyists have carried it to Raspberry Pi boards and Android phones (the Sandia Hellaphone project), with ports to other targets such as RISC-V reportedly in progress.
There is no significant commercial use today and no active language evolution. What remains is a small community of Plan 9 and Inferno enthusiasts, a complete and readable reference implementation, and a body of documentation - Ritchie’s language report, Brian Kernighan’s “A Descent into Limbo” tutorial, and Phillip Stanley-Marbell’s book Inferno Programming with Limbo (2003) - that makes the language unusually easy to learn for something so obscure.
Why It Matters
Limbo’s importance is mostly measured through Go. When Rob Pike, Ken Thompson, and Robert Griesemer began designing Go at Google in 2007, they brought the Bell Labs lineage with them, and Go’s FAQ states that its concurrency model descends from the CSP-inspired family that includes Newsqueak and Limbo. Go’s chan, select, and go statement are the direct heirs of Limbo’s chan, alt, and spawn; Go’s name-first declaration syntax, its := short declaration, and its packages-as-modules model all have visible Limbo ancestors. Anyone who has written a Go program has, in effect, written a Limbo program with the semicolons removed.
Beyond Go, Limbo was an early and thorough demonstration of the “safe language on a portable VM” approach to embedded and networked software - the same bet Java made, tried at the same time with a much smaller runtime and a more radical operating-system model. It showed that CSP-style concurrency could be made pleasant in a C-shaped language, that dynamically loaded modules could be typed and safe, and that a file-system namespace could serve as the loader, the RPC mechanism, and the security boundary all at once. Inferno lost the commercial contest with Java in the 1990s, but the ideas that Limbo carried are now in one of the most widely used languages in the world.
Timeline
Notable Uses & Legacy
Inferno operating system
Every user-level program in Inferno - the shell, the window manager, the Acme editor port, the Charon web browser, and the utilities - is written in Limbo and runs as Dis bytecode on the Inferno kernel or the hosted emu
Lucent PathStar access server
Lucent's PathStar combined voice/data switch used Inferno internally, with control software reportedly written in Limbo - one of the two Lucent products commonly cited as shipping on the system
Lucent VPN Firewall Brick
Lucent's network security appliance was the other internal Lucent product built on Inferno and Limbo
Hellaphone (Sandia National Laboratories)
Ran Inferno on top of Android's Linux kernel with the Java stack disabled, providing a phone dialer, SMS, editors, shell, compiler, web browser, mail client, and games written in Limbo, reportedly in well under a million lines of code
Vita Nuova grid and embedded products
Vita Nuova used Inferno and Limbo for grid-computing tools and embedded deployments across Windows, Linux, and Unix hosts and small ARM, MIPS, PowerPC, and x86 devices