hello, kanso
the toolchain is one binary. you build it with cargo, and it asks nothing else of your machine. clone, build, run — then the rest of this book has somewhere to land.
building the toolchain
there is no installer and no package index to register with. you clone the repository, build once with cargo, and the result is a single executable that answers to every command in this book.
$ 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 — we will write plain kanso from here on — and you have the whole toolchain. there is no project scaffold to generate and no manifest file asking you questions. a kanso program is a file, and the binary you just built knows how to run it, check it, test it, compile it to native code, fetch what it imports, and talk to you line by line.
the first program
here is hello world in its entirety—the file you just ran:
print "hello, world"
hello, world
print "hello, world" is an expression, and its value is a description of printing. the runtime receives that description and performs it; nothing happens as the parser walks past. chapter 05 is about what follows from that. for now, notice that the simplest program there is turns out to be a value.
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.
an entry file is a list of statements—no main, no ceremony marking where the program starts, because a file that runs starts at its first line. library files are the ones that declare things, and part ii covers how the two meet.
the verbs
the binary answers to eight commands. five are the daily cycle — run, check, test, build, repl — and three manage packages: install, list, update. you have already seen run. here is each of the others, once, doing its one job.
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:
$ 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:
$ kanso check hello.kso
hello.kso: ok
kanso test runs tests, and in kanso a test is just a constant whose name begins with test_. a test is a boolean value, and the runner evaluates it. no framework, no assertion library, no dsl to learn:
fn greet name
"hello, {name}"
test_greet = greet "kanso" == "hello, kanso"
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, a c compiler, to produce a standalone native binary. the default build is tuned for the loop—your program compiles unoptimized against a cached, already-optimized runtime, so warm builds land in tens of milliseconds. --release is the full-fat version: one optimization pass over the entire program at once, the slowest build, the fastest binary, and the mode every number in chapter 10 was measured under.
$ 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 — 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 value, ask whether it is legal, test it, run it, ship it as a binary, and poke at it interactively when you are unsure. the three package verbs matter once a program imports something it does not own, which is the last thing this book will teach you and the newest thing the toolchain does. everything else in this book is what goes inside the file.
reading your first compile error as a feature
most languages treat a compile error as a wall between you and running your program. kanso treats it as the compiler doing work you would otherwise do in review. the founding rule of the language is that anything a style guide, a linter, or a code reviewer would flag, the compiler flags first—so the whole class of "technically runs, but someone will make you change it" simply doesn't exist.
here is the smallest example. we bind a name and never use it:
name = "clay"
greeting = "hello"
print "hello, {name}"
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:
name = "clay"
print "hello, {name}"
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 order carries no meaning. it feels strict on day one and invisible by day three, because there was never a decision to make—the compiler already made it, identically, for everyone. the full list of what it will tell you lives in appendix a, the diagnostics catalog.
the repl, the playground, and --plan 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
you don't have to build anything to try kanso. the playground runs the interpreter in your browser: type a program, hit run, read the output—the same interpreter the repl uses, with nothing to install. it's the right place to follow along with this chapter if you're reading on a machine where you'd rather not clone a repo yet.
there's a second thing the interpreter can do that the browser makes vivid. before it runs a program, it can show you the plan—the description your program evaluated to, before a single effect fires:
$ kanso run hello.kso --plan
plan:
print "hello, world" # from line 1
that a program is a value, not an action, stops being a slogan the moment you can print the value and read it. the plan is exactly what the runtime would perform, laid out for inspection. chapter 05 builds whole programs you test this way—by asserting on the plan instead of mocking the world.
two engines, one meaning
most toolchains make you pick a side. go bought instant compiles and pays with slower binaries forever; rust bought maximum binaries and makes every developer wait. kanso declines the bundle by never asking the optimizer to be the dev loop. kanso run compiles unoptimized against a cached, already-optimized runtime; build --release compromises nothing; and the interpreter—the thing behind repl, the playground, and --plan—stands apart as the oracle both must answer to.
the price of having two engines is that they must agree, so they are forced to. ci builds every example in this book native and requires byte-identical output against the interpreter. a one-bit disagreement fails the build. you'll meet both engines properly in chapter 10, where the compiler gets to show off. until then, everything in this book runs under kanso run, and everything would produce the same bytes under kanso build.
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
this book has three parts and three appendices, and they build on each other 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 that make kanso itself: failure is an ordinary value, and effects are ordinary descriptions. it closes on flow and concurrency, and on modules and tests. by the end of part i you can read any program in the rest of the book.
part ii—real programs spends that vocabulary on things you'd actually ship: a json library that parses and prints, and a voting simulator with real logic to get wrong. these chapters are where the small rules turn out to have been load-bearing all along.
part iii—under the hood is the one chapter where the compiler stops being invisible and shows you how a language this strict ends up fast by construction—the two engines, the caching, and the numbers.
the three appendices are reference, not narrative: 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, and canonical form is the exact set of formatting rules the compiler enforces—the ones that made your unused binding an error.
you can now build the toolchain, run a program, check it, test it, compile it to a native binary, and talk to it in the repl. you have also met the compiler's temperament: clutter is an error, and it tells you where. one thread is left hanging from the first program — its single line was a value, and that value was a description. the next chapter starts where both engines start, with values and the names you bind them to, and with the errors kanso raises to keep those names honest.
exercises
- write and
kanso runa program that prints your own name. then add a second binding you never use, runkanso check, and read the diagnostic—confirm it names the exact line and column. - open the repl and bind a constant, then write an expression that uses it. predict the value before you press enter, then check yourself.
- run
kanso run hello.kso --planand thenkanso run hello.kso. describe, in one sentence, what changed between the two—and what stayed exactly the same. kanso build hello.ksoand run the resulting./hellodirectly. confirm its output is byte-for-byte identical tokanso run—the agreement ci enforces on every example in this book.