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

Vx for Rust programmers

Vx looks like Rust, so Rust that almost compiles is the first Vx most people write. This chapter lists what to change. Each item shows the Rust form and the Vx form.

Modules and items

  • import, not use. use core::option; is import core::option;. An import brings in everything the module declares.
  • No pub. Every top-level item is visible to a module that imports it.
  • No attributes. There is no #[derive(..)], #[test] or #[inline].

Functions and methods

  • The return type is always written. fn log(x : i32) is fn log(x : i32) -> void, and main returns i32: fn main() -> i32 { .. return 0; }.
  • self has a type. fn get(&self) is fn get(self : &Self), &mut self is self : &mut Self, and self is self : Self. The type’s own name works too: self : &Point.
  • Closure parameters have types. |x| x + 1 is | x : i64 | x + 1.
  • No impl Trait parameters. fn show(g : &impl Area) is fn show<T : Area>(g : &T).

Types and values

  • No turbofish. Vec::<i32>::new() is Vec<i32>::new().
  • An enum variant is written with its type. Some(3) is Option<i64>::Some(3), and None is Option<i64>::None(). Option comes from import core::option;.
  • An integer variable with no written type is i32. Rust reads let mut total = 0; as an i64 when i64s are added to it later. Vx does not, so write let mut total : i64 = 0;.
  • Arrays are tensors. [i32; 4] is Tensor<i32, [4]>. The shape is part of the type, and a ? marks a size known only at run time: Tensor<f32, [?, ?]>.
  • Uninitialized storage is unsafe. MaybeUninit has no equivalent. Write unsafe { Tensor<f32, [4]>::uninit() } and set every element before reading it, or use Tensor<f32, [4]>::new(), which is zeroed.

Control flow

  • A match arm’s body is a block. 0 => 7, is 0 => { 7 },.
  • No if let or while let. Use match.
  • No ? operator. Use match and return early.

Printing

  • print(x) prints one value with no newline.
  • print!("text") prints text. println!("{} and {}", a, b) puts each value in place of a {} and ends the line.
  • There is no function called println: println(x) is an error. Write println!("{}", x).

What Rust does not have

Rust has one kind of memory. Vx describes several, and checks where data lives:

  • A tensor’s third type argument is the memory it lives in: Tensor<f32, [4, 4], Memory::NPU_HBM>.
  • transfer(t, Memory::NPU_HBM) moves data to another memory. It is required even when the move costs nothing on the hardware.
  • spawn on(Topology::NPU[0]) { .. } runs a block on a device. Everything the block uses must already be in memory that device can reach.
  • unsafe allows raw pointers and calls to unsafe fn, as in Rust. It does not allow reading device memory from the host: that stays a compile error.

Topologies and memory explains these.

A complete program

Most of the differences above, in one program:

import core::iter;
import core::option;

trait Area {
    fn area(self : &Self) -> i64;
}

struct Rect {
    w : i64,
    h : i64,
}

impl Rect {
    fn grow(self : &mut Rect, by : i64) -> void {
        self.w = self.w + by;
        self.h = self.h + by;
    }
}

impl Area for Rect {
    fn area(self : &Rect) -> i64 {
        return self.w * self.h;
    }
}

fn total<T : Area>(a : &T, b : &T) -> i64 {
    return a.area() + b.area();
}

fn half(n : i64) -> Option<i64> {
    if n % 2 == 0 {
        return Option<i64>::Some(n / 2);
    }
    return Option<i64>::None();
}

// Rust would write `half(half(n)?)`.
fn quarter(n : i64) -> Option<i64> {
    match half(n) {
        Option<i64>::Some(h) => { return half(h); },
        Option<i64>::None() => { return Option<i64>::None(); }
    }
}

fn main() -> i32 {
    let mut r = Rect { w : 2, h : 3 };
    r.grow(1);
    let s = Rect { w : 1, h : 1 };
    println!("area {}", total(&r, &s));

    let squares = range(1i64, 4i64).map(| x : i64 | x * x).sum();
    println!("squares {}", squares);

    match quarter(12) {
        Option<i64>::Some(q) => { println!("quarter {}", q); },
        Option<i64>::None() => { println!("no quarter"); }
    }

    let mut t : Tensor<f32, [3]> = unsafe { Tensor<f32, [3]>::uninit() };
    for i in 0..3 {
        t[i] = 0.5 * (i as f32);
    }
    println!("last {}", t[2]);
    return 0;
}

It prints:

area 13
squares 14
quarter 3
last 1