Basics

Noname is a language that closely resembles Rust.

For example, in the following program you can see a main function:

fn main(pub public_input: Field, private_input: Field) {
    let x = private_input + public_input;
    assert_eq(x, 2);
}

The only differences with Rust are:

  • The pub keyword is used to mark public inputs. By default all arguments are private.
  • assert_eq is not a macro, there are no macros in noname.
  • a Field type is used as main types everywhere. It is defined in field.rs to be the pasta Fp field (the base field of the Pallas curve). If these words mean nothing to you, just see Field as a large number. Ideally programs should be written without this type, but for now custom types do not exist.

To run such a file, and assuming you have Rust installed, you can type in the terminal:

$ cargo run -- --path path/to/file.no --private-inputs '{"private_input": ["1"]}' --public-inputs '{"public_input": ["1"]}'

As you can see, inputs are passed with a JSON format, and the values are expected to be encoded in decimal numbers.

Builtins and use statements

Some builtin functions are available by default:

  • assert_eq to check that two field elements are equal
  • assert to check that a condition is true.

Like in Rust, you can also import other libraries via the use keyword. If you do this, you must know that you can only import a library, but not its functions (and types, and constants) directly.

For example, to use the poseidon function from the crypto library (or module), you must import std::crypto and then qualify your use of crypto::poseidon:

use std::crypto;

fn main(pub public_input: Field, private_input: [Field; 2]) {
    let digest = crypto::poseidon(private_input);
    assert_eq(digest[0], public_input);
}

Note that currently, only built-in libraries (written in Rust) are working. In the future we’d like for other libraries to be written in the noname language.

Field

The Field type is the primitive type upon which all other types are built. It is good to know about it as it is used in many places, and is error prone: it does not match the size of commonly-found types like u32 and u64 and can have unexpected behaviors as it can overflow or underflow without emitting an error.

Ideally, you should never use the Field type, but currently the library is quite limited and the ideal world is far away.

Note that you can define Field elements directly in the code by writing a decimal number directly. For example:

#![allow(unused)]
fn main() {
let x = 2;
assert(y, 4);
}

Arrays

While there are no dynamic arrays (or vectors), you can use fixed-size arrays like in Rust.

For the moment, I believe that arrays can only be declared in a function argument as the following declaration hasn’t been implemented yet:

#![allow(unused)]
fn main() {
let x = [1, 2, y];
}

Boolean

Booleans are similar to Rust’s boolean. They are currently the only built-in type besides Field and arrays.

#![allow(unused)]
fn main() {
let x = true;
let y = false;
assert(!(x & y));
}

Mutability

Variables are by default not mutable. To make a variable mutable, you must use the mut keyword:

#![allow(unused)]
fn main() {
let mut x = 1;
x = 2; // GOOD

let y = 1;
y = x + y; // BAD
}

For loops

fn main(pub public_input: Field, private_input: [Field; 3]) {
    let mut sum = 0;

    for i in 0..3 {
        sum = sum + private_input[i];
    }

    assert_eq(sum, public_input);
}

Constants

Like variables and function names, constants must be lowercase.

At the moment they can only represent field elements. Perhaps it would be nice to be able to represent different types in the future.

const player_one = 1;
const player_two = 2;

fn main(pub player: Field) -> Field {
    assert_eq(player_one, player);
    let next_player = player + 1;
    assert_eq(player_two, next_player);
    return next_player;
}

If Else statements

Currently, if/else statements are not supported. Only the ternary operator is:

fn main(pub xx: Field) {
    let plus = xx + 1;
    let cond = xx == 1;
    let yy = cond ? plus : xx ;
    assert_eq(yy, 2);
}

The two branches of the ternary operator must be variables (as in the xample), or array accesses (e.g. thing[0]), or field accesses (e.g. thing.field).

Functions

fn add(x: Field, y: Field) -> Field {
    return x + y;
}

fn double(x: Field) -> Field {
    return x + x;
}

fn main(pub one: Field) {
    let four = add(one, 3);
    assert_eq(four, 4);

    let eight = double(4);
    assert_eq(eight, double(four));
}

Custom types

struct Thing {
    x: Field,
    y: Field,
}

fn main(pub x: Field, pub y: Field) {
    let thing = Thing {
        x: 1,
        y: 2,
    };
    
    assert_eq(thing.x, x);
    assert_eq(thing.y, y);
}

Methods on custom types

struct Thing {
    x: Field,
    y: Field,
}

fn Thing.new(x: Field, y: Field) -> Thing {
    return Thing {
        x: x,
        y: y,
    };
}

fn Thing.verify(self, v: Field) {
    assert_eq(self.x, v);
    assert_eq(self.y, v + 1);
}

fn Thing.update_and_verify(self) {
    let new_thing = Thing {
        x: self.x + 1,
        y: self.y + 1,
    };

    new_thing.verify(2);
}

fn main(pub x: Field) {
    let thing = Thing.new(x, x + x);
    thing.update_and_verify();
}

technically you can even call static methods from a variable of that type:

#![allow(unused)]
fn main() {
let y = thing.new(3, 4);
}

Note

It’s not necessarily pleasant to read, and we could prevent it by storing some meta information (static_method: bool) in the type checker, but it’s not a big deal.

Early returns

TODO

Hints

TODO

Shadowing

We forbid variable shadowing as much as we can.

For example, this should not work:

#![allow(unused)]
fn main() {
let x = 2;
let x = 3; // this won't compile

let y = 4;
for i in 0..4 {
    let y = i; // this won't compile either
}
}