Getting started
This page makes a package, builds and runs it, and shows the commands you will use every day. It assumes novac is installed and on your PATH.
A new package
novac new hello
cd hellonovac new writes a manifest, a first source file, and instructions for AI coding agents:
hello/
├── nova.toml
├── src/
│ └── main.nova
├── AGENTS.md
└── .gitignoreAGENTS.md tells an agent working in the package what Nova is and how to check its work. With novac new hello --agent claude (or gemini, copilot, cursor) it also writes that agent's own file and configures Nova's MCP server for it; --no-agent writes none. See Nova for AI agents.
nova.toml names the package and says what it builds:
[package]
name = "hello"
version = "0.1.0"
kind = "executable"
[dependencies]and src/main.nova is a program:
fn main() {
Console.writeLine("Hello from hello");
}Hello from helloBuild and run
Inside a package, the commands need no file names:
novac build # compiles to C, then to out/hello
./out/hello
novac run --interp # runs the program in the reference interpreter instead
novac check # type and borrow checks only: the fastest way to find errors
novac fmt # formats the package's sources in the canonical styleEvery .nova file under src/ belongs to the package, and all of them share one scope: a function declared in one file can be called from another without an import.
A slightly larger program
Replace src/main.nova with:
/// The number of steps the Collatz sequence takes from `n` to 1.
fn steps(n: u64) -> u32 {
var value = n;
var count: u32 = 0;
while (value != 1) {
value = if (value % 2 == 0) value / 2 else 3 * value + 1;
count += 1;
}
return count;
}
fn main() {
var longest: u64 = 1;
for (n in 1..1000u64) {
if (steps(n) > steps(longest)) {
longest = n;
}
}
Console.write("longest below 1000: ");
Console.write(longest);
Console.write(", ");
Console.write(steps(longest));
Console.writeLine(" steps");
}longest below 1000: 871, 178 steps3 * value + 1 is checked: if it overflowed a u64, the program would stop with a message naming the line, in every build. Nova never wraps silently.
When the compiler says no
Errors name a code, the place, and usually the fix. This program keeps a reference to a vector's element and then grows the vector, which could move the element:
fn main() {
var numbers = Vector<i32>.create();
numbers.push(1)!;
let first = borrow numbers[0];
numbers.push(2)!;
Console.writeLine(first);
}main.nova:5:5: error[N0241]: cannot borrow 'numbers' as mutable while 'numbers[0]' is borrowed
|
5 | numbers.push(2)!;
| ^^^^^^^
= note: 'numbers[0]' is borrowed here (4:5)
= note: the borrow is used later here (6:5)The fix is to finish with first before the push, or to copy the value (let first = numbers[0];). Every code is explained in Diagnostics.
Files without a package
For a quick experiment, a package is not needed: give the files to the command.
novac build tool.nova helpers.nova -o tool
novac run --interp tool.novaNext
- Basics: values, types, functions and control flow.
- Packages: dependencies, the lock file, and sharing code.
- The novac command: every command and option.