chapter 01

hello, kanso

this chapter installs kanso, runs a first program, tours the commands you will use every day, and reads a first compile error. if you would rather not install anything yet, most of the programs in the first part of the book also run in the playground, in your browser.

building the toolchain

kanso is built from source. you need two things on your machine: a rust toolchain, for cargo, and clang, which kanso uses to turn your programs into native code. the book's own ci uses clang 19, and clang 18 works too. then clone the repository and build it once:

shell
$ git clone https://github.com/kanso-lang/kanso
$ cd kanso
$ cargo build --release
$ ./target/release/kanso run examples/hello.kso
hello, world

put target/release on your path, and from here on the book writes plain kanso. that one executable is the whole toolchain: it runs, checks, tests and builds programs, fetches the packages they import, and gives you an interactive prompt. there is no project scaffold to generate. a kanso program starts as a single file.

the first program

here is hello world in its entirety—the file you just ran:

hello.kso
print "hello, world"
kanso run hello.kso
hello, world

print "hello, world" is an expression, and its value is a description of printing. evaluating it prints nothing. the runtime receives the description when the program has been evaluated, and performs it. chapter 05 is about what follows from that.

notice also what isn't there. no parentheses around the argument—application is a space, print "hello, world". no semicolon. no import for print. no main to wrap it in. and no room for stylistic debate: this program has exactly one legal rendering, and you just read it.

a file like this one, made only of statements, is an entry: it runs from its first line. a file that declares functions or types is a library, and an entry imports it (chapter 07). most of the samples in the next few chapters are a third kind, a play file, which holds a few definitions beside the statements that use them and runs with kanso play. it is the form for small programs and for following along.

the verbs

the daily commands are run and play, check, test, build and repl. three more, install, list and update, manage packages, and kanso --help lists them all. you have already seen run. here is each of the others once.

kanso run is the dev loop, and you will live in it. it builds a cached native binary behind a content hash and executes it—tens of milliseconds when the program changed, about five when it didn't, with no compiler invocation at all on a warm cache:

shell
$ time kanso run hello.kso
hello, world
kanso run hello.kso  0.00s user 0.00s system  0.005 total

kanso check runs the front of the pipeline and stops before executing. it exists so editors and ci can ask "is this program legal?" and get a one-line answer—nothing runs, nothing prints, no effect happens:

shell
$ kanso check hello.kso
hello.kso: ok

kanso test runs tests. a test is a constant whose name begins with test_ and whose value is a boolean, and the runner evaluates each one and reports it. there is no test framework to set up:

greet_test.kso
fn greet name
  "hello, {name}"

test_greet = greet "kanso" == "hello, kanso"
kanso test greet_test.kso
test_greet ... ok
1 passed, 0 failed

kanso build is the other engine entirely. it turns your program into llvm ir—the portable, lower-level code that compilers like rustc and swift also target—then hands that to clang to produce a standalone native binary. the default build compiles your program unoptimized against a cached, already-optimized runtime, so a warm build takes tens of milliseconds. --release optimizes the whole program at once: the slowest build and the fastest binary, and the mode every number in chapter 15 was measured under.

shell
$ kanso build hello.kso
built ./hello (llvm ir at hello.ll)
$ ./hello
hello, world

and kanso repl is the interactive tier. expressions evaluate and print their value; declarations persist for the rest of the session. it is the fastest way to answer a small question—what does this arithmetic do, does this interpolation render the way i think—without touching a file:

kanso repl
kanso repl — expressions evaluate, declarations persist, :help for directives, ctrl-d exits
» tau = 6.28318
defined tau
» tau * 2
12.56636
» print "tau is {tau}"
tau is 6.28318

that is the cycle: write a program, ask whether it is legal, test it, run it, build it into a binary, and try things at the prompt when you are unsure. the package commands matter once a program imports code it does not own.

reading your first compile error as a feature

kanso's founding rule is that anything a style guide, a linter or a code reviewer would flag, the compiler flags first. so a compile error in kanso often reports something that would run fine and that a reviewer would have asked you to change.

here is the smallest example. we bind a name and never use it:

unused_check.kso
name = "clay"
greeting = "hello"
print "hello, {name}"
kanso check unused_check.kso
error[unused]: unused binding `greeting`
  --> unused_check.kso:2:1
   2 | greeting = "hello"
       ^

an unused binding is dead weight: a reader has to hold greeting in their head, decide whether it matters, and conclude that it does not. in kanso it is an error rather than a warning, so the program does not run until the clutter is gone. the diagnostic names the binding, points at the column, and stops. delete the line and the program is clean:

greeting.kso
name = "clay"
print "hello, {name}"
kanso run greeting.kso
hello, clay

the same machinery enforces one space where one space belongs, one blank line between top-level declarations, and alphabetical order in the few places where order carries no meaning, such as imports. the rules leave nothing to decide about layout, so two people writing the same program write the same text. the full list of what it will tell you lives in appendix a, the diagnostics catalog.

mugi the tanuki - runs the interpreter; pays for everything in boba pearls
mugi

the repl, --plan and run --interp are all me: the interpreter. ask, and i'll tell you what a program is about to do without firing a single effect. kanso run keeps a native binary warm behind a content hash: the speed is tsuru's, the meaning is mine. if your edit-run loop ever feels slower than your thoughts, file a bug; we consider it a personal failure.

the playground

the playground runs kanso in your browser with nothing to install. type a program, press run, and read the output. it compiles your program to webassembly through the same code generator the native builds use, and falls back to the interpreter when it cannot.

the interpreter can also show you a program's plan: the description the program evaluated to, before any of it is performed.

shell
$ kanso run hello.kso --plan
plan:
  print "hello, world"    # from line 1

the plan is what the runtime would perform, written out where you can read it. the comment on each step is the line that produced it. chapter 05 uses plans to show what larger programs will do.

two engines, one meaning

kanso has two ways to run a program. the native compiler is behind kanso run and kanso build: run keeps compiles fast by building your code unoptimized against an already-optimized runtime, and build --release optimizes everything. the interpreter is behind repl, --plan and run --interp, and it is the reference: it defines what a program means. a third engine runs in the browser: the playground compiles a program to webassembly in the tab, through the native compiler's emitter.

two engines are only useful if they agree, so ci runs every example in this book on both and requires byte-identical output. a one-byte difference fails the build. chapter 15 looks at the compiler closely. until then, everything in this book runs under kanso run, and everything would produce the same bytes under kanso build.

tsuru - folds your program until it is small enough to fly
tsuru

whatever mugi says your program means, my binary must say byte for byte, or neither of us ships. reach for kanso build when you want a release artifact; kanso run covers the other thousand times a day.

the shape of this book

the book has four parts and three appendices, meant to be read in order.

part i—the language is the road you're on. from here it runs through values and the names you bind them to, then types and the dispatch that replaces every conditional you'd reach for, then the two ideas at the center of kanso: failure is an ordinary value, and effects are ordinary descriptions. it continues with concurrency, modules and tests, and sequences. by the end of part i you can read any program in the rest of the book.

part ii—building and shipping builds two programs of your own from an empty directory, a command-line tool and a web service, then measures one, makes it cheaper and ships both, and ends with depending on a package someone else published.

part iii—real programs reads two larger programs closely: the standard library's json parser, and a simulator that compares voting methods.

part iv—under the hood shows how the compiler makes these programs fast, and how the project keeps them fast.

the appendices are for looking things up: the diagnostics catalog is every error the compiler can hand you and how to read it, the builtin reference is every function you get for free, canonical form is the exact set of formatting rules the compiler enforces (the ones that made your unused binding an error), recipes are short programs for everyday jobs, and idioms are the decisions the compiler leaves to you.

you can now build the toolchain, run a program, check it, test it, compile it to a native binary, and try expressions in the repl. you have seen that clutter such as an unused name is an error, and that the error says where it is. the next chapter covers values and the names you bind them to.

exercises

  1. write and kanso run a program that prints your own name. then add a second binding you never use, run kanso check, and read the diagnostic—confirm it names the exact line and column.
  2. open the repl and bind a constant, then write an expression that uses it. predict the value before you press enter, then check yourself.
  3. run kanso run hello.kso --plan and then kanso run hello.kso. describe, in one sentence, what changed between the two—and what stayed exactly the same.
  4. kanso build hello.kso and run the resulting ./hello directly. confirm its output is byte-for-byte identical to kanso run—the agreement ci enforces on every example in this book.