Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Your first program

Hello, 42

Every Vx program starts at main, which returns an i32.

fn main() -> i32 {
    let x : i32 = 21;
    return x * 2;
}

Save that as hello.vx and run it:

vxc --run hello.vx
[JIT] Translating to LLVM IR...
[JIT] Optimizing LLVM IR (-O0)...
[JIT] Compiling to native object (-O0)...
[JIT] Linking native executable...
[JIT] Executing native binary...
[JIT] Program exited with code: 42

--run compiles the program and executes it immediately, then propagates the program’s own exit code. A non-zero exit status from vxc --run is your program’s return value, not a compiler failure — this program genuinely exits 42.

Printing

print takes a value; print! takes a literal. Neither needs an import.

fn main() -> i32 {
    print!("the answer is ");
    print(42);
    print!("\n");
    return 0;
}

Compiling ahead of time

The JIT is convenient for iterating. For anything you intend to keep, compile to a native executable:

vxc -c hello.vx -o hello.o

vxc --help lists the other actions — --emit-mlir and --emit-llvm are the two you will reach for most when you want to see what the compiler did with your code.

Something with a shape to it

Types annotate a binding with :, let mut makes it mutable, and for ranges with ..:

fn sum_to(n : i32) -> i32 {
    let mut total : i32 = 0;
    for i in 0..n {
        total += i;
    }
    return total;
}

fn classify(x : i32) -> i32 {
    if x > 10 {
        return 1;
    } else if x == 10 {
        return 2;
    } else {
        return 3;
    }
}

fn main() -> i32 {
    print(sum_to(10));
    print!("\n");
    print(classify(15));
    print!("\n");
    return 0;
}

Diagnostics carry a code and a source span. Forget a return and the compiler says so directly:

Error[E3028] at 2:3: 'add' returns i32 but its body can finish without returning a value

Every code the compiler can emit is listed in the diagnostic index, grouped by the stage that raises it.

Arrays and tensors

An array literal is a tensor, and indexing reads an element back:

fn main() -> i32 {
    let a : Tensor<f32, [4]> = [ 1.0, 2.0, 3.0, 4.0 ];
    print(a[0]);
    print!(" ");
    print(a[3]);
    return 0;
}

Tensor<f32, [4]> is a tensor of four f32 with its shape known at compile time. A ? stands in for a dimension that is not — Tensor<f32, [?, ?]> is a matrix whose extents are runtime values, which you read with .extent(0) and .extent(1).

Structs and methods

struct Point {
    x: i32,
    y: i32,
}

impl Point {
    fn magnitude_squared(self: &Point) -> i32 {
        return self.x * self.x + self.y * self.y;
    }
}

fn main() -> i32 {
    let p = Point { x: 3, y: 4 };
    return p.magnitude_squared();
}

The receiver is written out in full: self: &Point borrows it, self: &mut Point borrows it mutably. There is no implicit self.

Where to go next

You now have enough to write ordinary programs. Two directions from here:

  • A tour of Vx covers the rest of the language — generics, enums, pattern matching, ownership — none of which involves an accelerator.
  • Topologies and memory is the part that makes Vx different from every other systems language: placing data in a named memory space and having the compiler check it.