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.

Noir’s control flow syntax closely follows Rust. One important difference is that loop and while are only available in unconstrained contexts — constrained code requires that the number of loop iterations is known at compile time.

if / else

if is an expression in Noir, not just a statement. Parentheses around the condition are not required:
let a = 0;
let mut x: u32 = 0;

if a == 0 {
    if a != 0 {
        x = 6;
    } else {
        x = 2;
    }
} else {
    x = 5;
    assert(x == 5);
}
assert(x == 2);
Because if is an expression, you can use it on the right-hand side of a binding:
let x = if condition { 1 } else { 0 };
Noir has no || or && operators. Use the bitwise | and & operators instead — they are equivalent for booleans but do not short-circuit (which would be inefficient in ZK circuits).
if (my_val > 6) | (my_val == 0) {
    // ...
}

for loops

for loops iterate over a compile-time-known range. The index type is u64:
for i in 0..10 {
    // runs 10 times: i = 0, 1, ..., 9
}
Use ..= for an inclusive range:
for i in 0..=10 {
    // runs 11 times: i = 0, 1, ..., 10
}
for loops in constrained code must iterate over a range whose bounds are compile-time constants. You cannot loop over a range derived from a program input unless in an unconstrained context.

loop (unconstrained only)

loop creates an infinite loop that must be exited with break. It is only valid in unconstrained code:
unconstrained fn count_down(start: u32) {
    let mut i = start;
    loop {
        println(i);
        i -= 1;
        if i == 0 {
            break;
        }
    }
}
A loop must contain at least one reachable break statement.

while (unconstrained only)

while loops run as long as a condition is true. They are only valid in unconstrained code:
unconstrained fn example() {
    let mut i = 0;
    while i < 10 {
        println(i);
        i += 2;
    }
}

break and continue

break and continue are only available inside unconstrained code. They cannot jump out of more than one loop at a time:
unconstrained fn example() {
    for i in 0..10 {
        println("Iteration start");

        if i == 2 {
            continue; // skip to next iteration
        }

        if i == 5 {
            break; // exit the loop
        }

        println(i);
    }
    println("Loop end");
}
  • break exits the current loop and jumps to the statement after it.
  • continue stops the current iteration and jumps to the start of the next one. The iteration variable i is still incremented normally.

assert and assert_eq

assert enforces a constraint. If the condition is false, proof generation fails:
fn main(x: Field, y: Field) {
    assert(x != y);
}
Provide an optional failure message as the second argument:
assert(x == 10, "x must be 10");
assert(x != y, f"expected x != y, got x={x}");
assert_eq is shorthand for asserting two values are equal:
fn main() {
    let array = [1, 2];
    assert_eq(array[0], 1);
    assert_eq(array[1], 2, "second element must be 2");
}
Prefer assert_eq over assert(a == b) — it provides clearer error messages when verification fails.

Shadowing

Noir supports variable shadowing. A new let binding with the same name as an existing variable creates a new binding in the current scope:
let x = 5;
let x = x + 1; // x is now 6
{
    let x = x * 2; // x is 12 inside this block
    assert(x == 12);
}
assert(x == 6); // outer x is still 6
Unlike reassignment, shadowing allows you to change the type of a variable:
let x: Field = 5;
let x: u32 = 5; // ok: x is now u32

Mutability

Variables are immutable by default. Use mut to allow reassignment:
let mut x: u32 = 0;
x = 6; // ok

let y = 3;
// y = 4; // error: y must be mutable
The mut modifier can apply to patterns:
let (a, mut b) = (1, 2);
// a = 11; // error
b = 12;    // ok

let mut (c, d) = (3, 4);
c = 13; // ok
d = 14; // ok

Build docs developers (and LLMs) love