The tools

One binary, nx, and inside it everything the book has used and a few things it has not. All of them are views over the same compiler, which is why they agree with each other, and all of them are Nexium programs in the compiler's self/ directory.

Building and running#

commandwhat it does
nx run file.nx [-- args]build and run
nx build file.nx [-o out]build an executable (or an object file when there is no main)
nx test file.nx [--filter s] [--verbose]run the test blocks
nx bench file.nx [filter] [--quick]measure the bench blocks: an optimized build, the median time per iteration
nx debug file.nx [-- args]build for debugging and run under gdb or lldb
nx check file.nxcheck without building; the diagnostics only
nx emit-c file.nxprint the generated C

--mode debug (the default: checks on, symbols in), safe (optimized, checks on), fast (optimized, some checks proven away), small (size first). --target <triple> cross-compiles; --cc clang names a C compiler; --keep-c leaves the C file in nx-out/.

A debug build's C carries #line directives, so a debugger, a sanitizer's report and the C compiler all name lines of the .nx file. nx debug file.nx builds that way and starts gdb (lldb on macOS; NX_DEBUGGER names another) with formatters that show a String as text, a List or a slice as its elements, a Map as its entries, an optional as its value or null, and a struct's fields by their names: break file.nx:12, then run, then info locals. The C compiler numbers the locals (total_3 for total), which info locals shows.

--sanitize undefined builds with the C compiler's checks for undefined behaviour, and --sanitize address (gcc or clang, through NX_CC; zig cc has no runtime for it) with AddressSanitizer; address,undefined asks for both. In fast mode, which drops the overflow and bounds checks it cannot prove away, they put those checks back as a report, and they watch what the compiler cannot: the C that extern calls and unsafe blocks reach. nx test --mode fast --sanitize undefined is how to test a program that ships in fast mode.

Looking at a program#

nx effects prints the inferred effects of every function (chapter 15). nx audit lists every unsafe block and mutable global: the places the compiler's promises stop. nx refcounts lists every place a reference is retained or released:

$ nx refcounts topo/code/ownership.nx
reference count traffic: 13 site(s)

topo/code/ownership.nx:61:5  release at scope exit
    in main
topo/code/ownership.nx:61:17  allocation with count 1
    in main
topo/code/ownership.nx:61:43  retain (copy of a reference)
    in main

nx leaks runs a debug build with every allocation counted and reports what was alive at exit. nx size builds in small mode and attributes the executable's bytes to declarations, which answers "what is this binary made of":

$ nx size topo/code/hello.nx
    bytes  kind  declaration
     1025  code  main (entry point)
      256  data  (section .tls$$nx_tls_last_panic)
       73  data  (section .rdata)
       38  data  (string literals)
     1072        runtime and libc glue
     2472        total (small mode; the linker drops unreferenced sections)

Two and a half kilobytes of program for hello, world, which is what a runtime that is a header of static inline functions buys.

Formatting and documentation#

nx fmt file.nx rewrites the file in the one canonical layout; --check only reports, and is what CI runs on every file in this repository. The formatter settles spacing and never joins or splits lines, so the shape of your code stays yours.

nx doc file.nx renders the /// comments, signatures and inferred effects of a file to HTML, one page per file; the standard library reference is built from the same comments.

nx fix file.nx makes the edits the checker offers with its errors, where the fix is mechanical and keeps what the program does: .clone() where a value moves out from under a view of it (chapter 8), @escape(...) around a value kept past its arena, _ = in front of a value nothing uses. Each is kept only if the program then checks with fewer errors, and --check reports without writing. It will also migrate deprecated forms, when there are any; the stability policy says how one would be introduced.

The editor and the prompt#

nx lsp is the language server: diagnostics on every edit, hover with types and inferred effects, go to definition, completion and rename. When the program checks, definition and rename answer from the checker, so p.area() goes to the area of p's own type and renaming a field renames it in every file. The VS Code extension in the repository starts it; any editor that speaks the protocol can. nx repl (chapter 4) is the prompt.

Shipping#

nx ship file.nx (chapter 20) produces the file's declared artifacts; --target cross-compiles them. nx init, nx add and nx fetch (chapter 19) manage a manifest.

The installation#

nx version prints the version and its release name. nx doctor prints which C compiler will run and why, whether it answers, the standard library modules, and whether nx is on the PATH; it is the first thing to run when a build does something odd.

Under the hood#

Every one of these is a file in the compiler's source: self/fmt.nx, self/doc.nx, self/tools.nx (effects, audit, refcounts), self/size.nx, self/lsp.nx, self/repl.nx, self/ship.nx, self/manifest.nx, and the driver self/nx.nx that dispatches to them. The architecture tour walks through the compiler itself, stage by stage; the tools are the shorter reads, and self/tools.nx at a hundred lines is the shortest.

Next: the summit.

Exercises#

The compiler grades these: nx topo 22 writes them into a directory, nx topo check runs them. Or do them here: the code is editable, Run runs it with the compiler in the page (it loads on the first run and never sends your code anywhere), Check grades it as nx topo check would, and Reset puts the exercise back. Ctrl+Enter runs.

Quizno peeking

  1. nx effects on this file: which effects does total carry?
    fn total(xs: []i64) -> i64 {
        var t: i64 = 0
        for x in xs { t += x }
        return t
    }
    • (pure)
    • panics
    • allocates panics
    Answer

    panics. nx explain file.nx total panics shows the line that brings the effect in; +%= would make total pure.

    The compiler says:

    panics: arithmetic may overflow (use `+%` to wrap or `+|` to saturate) at main.nx:3:19
  2. And names?
    fn names() -> List(String) {
        var out = List(String).new()
        out.append(String.from("Herzog"))
        return out
    }
    • allocates
    • allocates panics
    • (pure)
    Answer

    allocates. Building the list and the string allocates; appending to a list that owns its growth cannot fail any other way.

    The compiler says:

    allocates: building a String allocates at main.nx:3:16
  3. nx check on this program: which line does the compiler reject?
    fn consume(own s: String) -> usize {
        return s.len
    }
    
    fn main() {
        let s = String.from("summit")
        let n = consume(s)
        println("{} {}", .{n, s})
    }
    • it compiles
    • line 7
    • line 8
    • line 2
    Answer

    line 8. own moves s into consume on line 7; line 8 reads it afterwards, which is the error, not the call.

    The compiler says:

    use of `s` after it was moved
  4. nx run: what does it print?
    fn main() {
        let small: u8 = 250
        println("{} {}", .{small +% 10, small +| 10})
    }
    • 260 260
    • 4 255
    • 4 4
    Answer

    4 255. +% wraps around 256 and +| stops at the largest u8; neither can panic, so main has no panics from them.