Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

CobaltC is a small systems programming language. It gives you what C gives you: control over memory, predictable layout, threads, direct calls into C libraries, and programs that compile to native code and run fast. It takes away one thing, the obligation to remember.

When you write C, your program depends on a long list of facts that appear nowhere in its text. This pointer is still valid. That integer cannot overflow here. This buffer was initialised before it was read. Nobody else is writing to that struct right now. This file handle is closed exactly once. The compiler checks none of them. You keep them in your head, and when one slips, the program is wrong in a way that may not show for months.

CobaltC is built around one idea: the facts that correct low-level code depends on should live in the language, not in the programmer’s memory. Every fact of that kind is either represented and checked by the language, or written down by you, in one place, as something you are asserting. There is no third option where a fact is simply assumed.

What that means in practice

Arithmetic is checked. If a + b does not fit its type, the program stops with a named fault instead of wrapping silently. When you want wrapping, you ask for it by name.

Every value that owns something, a buffer, a file, a thread, has exactly one owner. Giving it to someone else is a move, and the old name is no longer usable. When the owner goes out of scope, the thing it owned is released, in a fixed order, whether or not you remembered to release it.

References come in two kinds, shared and exclusive, and the language checks at every use that no exclusive reference is live at the same time as any other reference to the same storage. Most of these checks happen before the program runs. The ones that cannot be decided from the text happen while it runs, and a failure there stops the program with a diagnostic rather than reading freed memory.

A variable is never read before it is written. A reference never outlives the thing it points to. Two threads never race on one location. A value that came from outside the program, from a file or a socket, is bytes until you have validated it, and the language keeps the distinction.

Where the language cannot check a fact, because the fact is about memory it did not allocate or code it did not compile, you assert it inside an unsafe block. That block is a sentence in your program, visible to every reader, and a search for the word unsafe finds every assumption the program makes.

What CobaltC leaves out

A language earns its size by what it refuses. CobaltC has no inheritance, no traits or interfaces of your own, no exceptions, no garbage collector, no implicit conversions, no operator overloading, no macros, and no method-call syntax: you write Vec::push(&mut v, x), never v.push(x). Each absence is a decision, and the ones that matter to a programmer are explained where they come up. The result is a language you can hold in your head after a few days.

Two implementations

CobaltC comes with two tools that share one front end, so they accept and reject exactly the same programs with exactly the same messages.

coby is the interpreter. It runs a program straight from its source, doing every check the language requires on every operation. There is no build step, so it is the quickest way to run a program or try something out, and when the book says a program prints something, coby is what printed it.

cobc is the compiler. It translates a checked program to C and hands that to GCC or Clang, which produces a native executable. The compiled program keeps every run-time check the language requires except the ones the compiler proves can never fail, so it behaves exactly like the interpreted one and runs many times faster. The project’s tests hold the two to that promise on every program in this book.

How the book is arranged

Part I is practical. The next chapter installs the tools and runs a first program. The chapter after it builds a complete small program, a word counter, and uses most of the language once, lightly, so that the detailed chapters have something to refer back to.

Part II explains the language one idea at a time. Chapters 4 and 5 cover values and control flow, which will feel familiar. Chapters 6 and 7, ownership and borrowing, are the heart of the language and the part most worth reading slowly. The remaining chapters cover data types, errors, collections and text, generics and closures, modules, threads, and the boundary with C.

Part III builds a larger program, a command-line to-do list that keeps its data in a file, and then points you at the rest of the documentation.

You can read the whole book in a few evenings. You do not have to: a reader in a hurry can read Part I, then chapters 6 and 7, and look the rest up as they need it.