Hello, world

Put this in a file called hello.nx:

topo/code/hello.nx
fn main() {
    println("hello, world", .{})
    let name = "Nexium"
    let year = 2026
    println("hello from {}, in {}", .{name, year})
}

and run it:

$ nx run hello.nx
hello, world
hello from Nexium, in 2026

That is the whole ceremony. Now, what happened.

What nx run did#

nx run read the file, checked it, wrote a C file for it, handed the C file to the C compiler, and ran the executable that came out. On a warm machine the whole thing takes well under a second; you will not notice the C compiler unless you look for it. nx build hello.nx does everything but the last step and leaves nx-out/hello (hello.exe on Windows) behind.

You can look at the C if you are curious:

nx emit-c hello.nx

It is readable. Every Nexium function becomes a C function; the runtime is a single header pasted at the top. There is no virtual machine and no runtime library to install: the executable depends on the C library of the platform and nothing else.

Reading the program#

fn main() {

A program starts at main. This one takes no arguments and returns nothing; later chapters use fn main() -> !void (it can fail with an error) and fn main() -> u8 (it chooses its exit code).

    println("hello, world", .{})

println prints a line. The second argument is always there: it is the list of values to format, and here there are none. .{} is an anonymous list of values; you will see the same spelling wherever a function takes "some values": struct literals use .{ .x = 1 } for the same reason.

    let name = "Nexium"
    let year = 2026

let binds a name to a value, once. name is text; year is an integer, and since nothing says otherwise it is an i64. Types are inferred inside functions and written out on function signatures, so a function's contract is always visible.

    println("hello from {}, in {}", .{name, year})

Each {} in the format string takes the next value. The formatting is checked when the program is compiled: a {} without a value, or a value the formatter cannot print, is a compile error, not a surprise at run time.

Statements end at the end of the line. There are no semicolons, unless you want two statements on one line (a += 1; b += 1), and a line that ends in an operator or an open bracket continues on the next.

Comments#

// a line comment

/// A doc comment: it attaches to the declaration below it, and
/// `nx doc` turns it into documentation.
fn documented() { }

Two things to know before the next chapter#

Names. Types are PascalCase, functions and variables snake_case, constants SCREAMING_SNAKE_CASE. The compiler does not enforce this, but everything you read will follow it.

nx fmt. There is one formatting and a tool that applies it: nx fmt hello.nx rewrites the file in place; nx fmt hello.nx --check only reports. The formatter never joins or splits your lines, it settles spacing, so it is safe to run on anything.

Next: your first script, a program that does something useful.

Exercises#

The compiler grades these: nx topo 2 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.

fill_greetingfill in the blank

Replace ??? so the program greets the mountain by name.

fill_greeting.nx
fn main() {
    let peak = "Annapurna"
    println("hello, {}", .{???})
}
The recorded output
hello, Annapurna
Hints
  • println takes a format string and a .{ } list of arguments
Solution
fn main() {
    let peak = "Annapurna"
    println("hello, {}", .{peak})
}

predict_pitchespredict the output

Write what this prints into predict_pitches.guess before you run it.

predict_pitches.nx
fn main() {
    for i in 1..4 { println("pitch {}", .{i}) }
    println("done", .{})
}
What does it print?
pitch 1
pitch 2
pitch 3
done
Hints
  • a range 1..4 stops before 4