Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/noir-lang/noir/llms.txt

Use this file to discover all available pages before exploring further.

Every value in Noir has a type, which determines which operations are valid for it. All values in Noir are fundamentally composed of Field elements. Abstractions are added on top to introduce different data types. Noir has two categories of data types: primitive types (e.g. Field, integers, bool) and compound types that group primitive types (e.g. arrays, tuples, structs).
All primitive types in Noir are private by default. Mark values as pub when they should be revealed to the verifier. This meaning of pub on a type (e.g. pub Field) is distinct from pub on a function (e.g. pub fn foo()).

Field

The Field type corresponds to the native field type of the proving backend. Its size depends on the elliptic curve used — for example, 254 bits when paired with the default Grumpkin curve backend. Fields support standard integer arithmetic:
fn main(x: Field, y: Field) {
    let z = x + y;
}
If proving efficiency is a priority, prefer Field as the default type. Smaller integer types like u64 incur extra range constraints.

Field methods

Decomposes a field element into an array of N bits, in little-endian or big-endian order. Fails if the value exceeds 2^N.
pub fn to_le_bits<let N: u32>(self: Self) -> [u1; N]
pub fn to_be_bits<let N: u32>(self: Self) -> [u1; N]
fn main() {
    let field: Field = 10;
    let bits = field.to_le_bits::<8>();
    // bits = [0, 1, 0, 1, 0, 0, 0, 0]  (10 in binary, little-endian)
    assert(bits[1] == 1);
    assert(bits[3] == 1);
}
Adds a constraint that the field can be represented with at most bit_size bits.
fn main() {
    let field = 2;
    field.assert_max_bit_size::<32>();
}
sgn0 returns the parity of the field element (0 if even, 1 if odd). lt returns true if the field is less than another field.
fn sgn0(self) -> u1
pub fn lt(self, another: Field) -> bool

Integers

An integer type is a range-constrained field type. Noir supports both unsigned and signed integers.
Unsigned integers are prefixed with u followed by the bit size: u8, u16, u32, u64, u128.
fn main() {
    let x: u8 = 1;
    let y = 1_u8;
    let z = x + y;
    assert(z == 2);
}
A u8 can store 0 to 255; a u32 can store 0 to 4,294,967,295.
When an integer literal appears without an explicit type, it defaults to Field unless another type is inferred. Loop indices default to u64. Use type suffixes like 1_u32 or 3i64 to be explicit.

Booleans

The bool type has two values: true and false.
fn main() {
    let t = true;
    let f: bool = false;
}
Booleans are most commonly used in if expressions and assert statements.
Noir does not support the short-circuit logical operators || and &&. Use the bitwise operators | and & instead — they behave identically for booleans without the short-circuit evaluation, which is inefficient for ZK backends.
let my_val = 5;
let mut flag = 1;
if (my_val > 6) | (my_val == 0) {
    flag = 0;
}

Strings

The string type is a fixed-length value defined with str<N>.
fn main(message: pub str<11>, hex_as_string: str<4>) {
    println(message);
    assert(message == "hello world");
    assert(hex_as_string == "0x41");
}
Convert a string to bytes with as_bytes() or as_bytes_vec():
fn main() {
    let message = "hello world";
    let message_bytes = message.as_bytes();
    assert(message_bytes.len() == 11);
    assert(message_bytes[0] == 104); // 'h'
}
EscapeDescription
\rCarriage return
\nNewline
\tTab
\0Null character
\"Double quote
\\Backslash
let s = "Hello \"world\""; // Hello "world"
let s = "hey \tyou";      // hey   you

Arrays

Arrays group values of the same type into a fixed-size collection. The syntax is [T; N].
fn main(x: u64, y: u64) {
    let my_arr = [x, y];
    let your_arr: [u64; 2] = [x, y];
}
Access elements by index, mutate with mut, and initialize with a repeated value:
fn main() {
    let mut arr = [1, 2, 3, 4, 5];
    assert(arr[0] == 1);
    arr[0] = 42;
    assert(arr[0] == 42);

    // Initialize 32 zeros
    let zeros: [Field; 32] = [0; 32];
}
Multidimensional arrays are supported:
let matrix: [[Field; 2]; 2] = [[1, 2], [3, 4]];
let element = matrix[0][0];

Array methods

fn main() {
    let array = [42, 42];
    assert(array.len() == 2);

    let arr = [42, 32];
    let sorted = arr.sort();
    assert(sorted == [32, 42]);

    let sorted_desc = arr.sort_via(|a, b| a >= b);
    assert(sorted_desc == [42, 32]);
}
let a = [1, 2, 3];
let b = a.map(|x| x * 2); // [2, 4, 6]

fn main() {
    let arr = [2, 2, 2, 2, 2];
    let folded = arr.fold(0, |a, b| a + b);
    assert(folded == 10);

    let reduced = arr.reduce(|a, b| a + b);
    assert(reduced == 10);
}
fn main() {
    let arr = [2, 2, 2, 2, 2];
    assert(arr.all(|a| a == 2));

    let arr2 = [2, 2, 2, 2, 5];
    assert(arr2.any(|a| a == 5));

    let arr1 = [1, 2, 3, 4];
    let arr3 = [6, 7, 8, 9, 10, 11];
    let concatenated = arr1.concat(arr3);
    assert(concatenated == [1, 2, 3, 4, 6, 7, 8, 9, 10, 11]);
}
Arrays in Noir are fixed size. Use dynamic indexing (non-constant indices) with care — it incurs a slight runtime cost. Mutable references to array elements are not supported.

Vectors

A vector ([T]) is a dynamically-sized sequence of elements. Vectors can be resized at runtime and are written with the @ prefix literal syntax.
Vectors are only usable in unconstrained contexts. They cannot be returned from a constrained circuit.
fn main() -> pub u32 {
    let mut vector: [Field] = @[0; 2];
    let mut new_vector = vector.push_back(6);
    new_vector.len()
}

Vector methods

// push_back: adds element to end, returns new vector
let mut v: [Field] = @[0; 2];
let v2 = v.push_back(6);

// push_front: adds element to front
let mut v: [Field] = @[];
let v2 = v.push_front(20);
assert(v2[0] == 20);

// pop_front / pop_back
let (first, rest) = v2.pop_front();
let (init, last) = v2.pop_back();

Tuples

A tuple collects multiple values of potentially different types:
fn main() {
    let tup: (u8, u64, Field) = (255, 500, 1000);
}
Access tuple elements via destructuring or direct index access:
fn main() {
    let tup = (1, 2);
    let (one, two) = tup; // destructuring

    let tup2 = (5, 6, 7, 8);
    let five = tup2.0;    // direct index
    let eight = tup2.3;
}

Structs

Structs group values of different types under named fields:
struct Animal {
    hands: Field,
    legs: Field,
    eyes: u8,
}
Create instances with field initialization syntax. Fields can be provided in any order:
fn main() {
    let legs = 4;
    let dog = Animal {
        eyes: 2,
        hands: 0,
        legs,       // shorthand when variable name matches field name
    };
    let zero = dog.hands;
}
Structs can be destructured in patterns, with optional field renaming:
fn main() {
    let Animal { hands, legs: feet, eyes } = get_octopus();
    let ten = hands + feet + eyes as Field;
}

impl blocks

Methods on structs are defined in impl blocks:
struct MyStruct {
    foo: Field,
    bar: Field,
}

impl MyStruct {
    fn new(foo: Field) -> MyStruct {
        MyStruct { foo, bar: 2 }
    }

    fn sum(self) -> Field {
        self.foo + self.bar
    }
}

fn main() {
    let s = MyStruct::new(40);
    assert(s.sum() == 42);
}

Visibility

// Public struct with mixed field visibility
pub struct Animal {
    hands: Field,           // private to its module
    pub(crate) legs: Field, // accessible from the entire crate
    pub eyes: u8,           // accessible from anywhere
}

References

Noir supports mutable references using &mut T. Use & to reference a variable and * to dereference it:
fn main() {
    let mut x = 2;
    multiplyBy2(&mut x);
}

fn multiplyBy2(x: &mut Field) {
    *x = *x * 2;
}
Mutable references enable non-local mutation:
fn set_to_zero(x: &mut Field) {
    *x = 0;
}

fn main() {
    let mut y = 42;
    set_to_zero(&mut y);
    assert(*y == 0);
}
Mutable references can be stored in structs (no lifetime annotations are required):
struct Foo {
    x: &mut Field
}

impl Foo {
    fn incr(mut self) {
        *self.x += 1;
    }
}

fn main() {
    let foo = Foo { x: &mut 0 };
    foo.incr();
    assert(*foo.x == 1);
}
Mutable references to array elements are not supported. Store the element in a fresh variable first.

Function types

Noir supports higher-order functions. A function type is written as:
fn(arg1_type, arg2_type, ...) -> return_type
fn assert_returns_100(f: fn() -> Field) {
    assert(f() == 100);
}

fn main() {
    assert_returns_100(|| 100); // ok
    assert_returns_100(|| 150); // fails
}
Closures that capture variables have a capture environment type. To accept both closures and regular functions, make the environment generic:
fn foo<Env>(f: fn[Env]() -> Field) -> Field {
    f()
}

fn main() {
    let (x, y) = (50, 50);
    assert(foo(|| x + y) == 100);
    assert(foo(|| 60) == 60);
}

Type aliases

A type alias creates a new name for an existing type:
type Id = u8;

fn main() {
    let id: Id = 1;
    let zero: u8 = 0;
    assert(zero + 1 == id);
}
Type aliases can be generic and numeric:
type Double<let N: u32>: u32 = N * 2;

fn concat_self<let N: u32>(array: [u32; N]) -> [u32; Double::<N>] {
    let mut result = [0; Double::<N>];
    for i in 0..array.len() {
        result[i] = array[i];
        result[i + array.len()] = array[i];
    }
    result
}

Type coercions

The compiler automatically coerces between certain type pairs without an explicit cast:
Actual typeExpected type
[T; N][T]
fn(..) -> Runconstrained fn(..) -> R
str<N>CtString
fmtstr<N, T>CtString
&mut T&T
fn requires_vector(_vector: [Field]) {}

fn main() {
    let array: [Field; 4] = [1, 2, 3, 4];
    // Array is automatically coerced to a vector
    requires_vector(array);
}
Coercions are only applied at the outermost type level, never within nested types.

Build docs developers (and LLMs) love