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.

Functions in Noir follow Rust semantics. Unlike Rust, Noir does not support early return statements — the last expression in a function body is its return value.

Basic syntax

Declare a function with the fn keyword:
fn foo() {}
Parameters must include explicit types, separated by commas. The return type follows the -> arrow:
fn foo(x: Field, y: Field) -> Field {
    x + y
}
Use _ or a _-prefixed name for unused parameters:
fn foo(_: Field, y: Field) -> Field {
    y
}

fn bar(_x: Field, y: Field) -> Field {
    y
}

Visibility

By default, functions are visible only within their package. Use pub to expose them to other packages, or pub(crate) to expose them within the same crate only:
pub fn foo() {}          // visible to all packages
pub(crate) fn bar() {}   // visible only within this crate

The main function

In binary crates, main is the entry point. All its parameters must have a size known at compile time:
fn main(x: Field) {}             // ok: single Field
fn main(x: [Field; 2]) {}        // ok: fixed-size array
fn main(x: (Field, bool)) {}     // ok: tuple of known size
fn main(x: str<5>) {}            // ok: fixed-length string

// fn main(x: [Field]) {}        // error: variable-size array
Tests don’t differentiate between main and other functions — a test can pass even when the program itself would fail to compile or prove. Always run nargo check to validate your main signature.

Call expressions

Call a function by name, passing arguments:
fn main(x: Field, y: Field) {
    let z = foo(x);
}

fn foo(x: Field) -> Field {
    x + x
}

Methods on structs

Define methods on any struct using impl blocks. The first parameter self refers to the receiver:
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);

    // Methods are syntactic sugar; these are equivalent:
    assert(s.sum() == 42);
    assert(MyStruct::sum(s) == 42);
}

Specialization on generics

Implementations can be specialized for specific generic type arguments:
struct Foo<T> {}

impl Foo<u32> {
    fn foo(self) -> Field { 1 }
}

impl Foo<u64> {
    fn foo(self) -> Field { 2 }
}

fn main() {
    let f1: Foo<u32> = Foo{};
    let f2: Foo<u64> = Foo{};
    assert(f1.foo() + f2.foo() == 3);
}
Overlapping impl blocks — for example, one for Foo<u32> and a blanket impl<T> Foo<T> — are not allowed. Noir must be able to unambiguously select which implementation to use.

Lambdas

Lambdas are anonymous functions using the |args| expr syntax:
let add_50 = |val| val + 50;
assert(add_50(100) == 150);
Lambdas can have a block body:
let cool = || {
    let x = 100;
    let y = 100;
    x + y
};
assert(cool() == 200);

Closures

A lambda that captures variables from its enclosing scope is called a closure:
fn main() {
    let x = 100;
    let closure = || x + 150;
    assert(closure() == 250);
}
The closure’s type encodes its capture environment as fn[Env](args) -> ret. To accept both regular functions and closures, make the environment type 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);
}

Mutability

Variables are immutable by default. Declare mutable variables with mut:
let x = 2;
// x = 3; // error: x must be mutable

let mut y = 3;
y = 4; // ok
Mutability in Noir is local — mutating a parameter inside a function does not affect the caller:
fn main() -> pub Field {
    let x = 3;
    helper(x);
    x // still 3
}

fn helper(mut x: i32) {
    x = 4; // only affects local copy
}
Use mutable references (&mut T) for 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);
}

Global variables

Globals are module-level constants with explicit types:
global N: Field = 5;
global TUPLE: (Field, Field) = (3, 2);

fn main() {
    assert(N == 5);
    assert(N == TUPLE.0 + TUPLE.1);
}
Globals initialized to integer literals can be used to specify array lengths:
global N: u32 = 2;

fn main(y: [Field; N]) {
    assert(y[0] == y[1]);
}
Use pub or pub(crate) to control global visibility:
pub global N: u32 = 5;

Attributes

Attributes apply metadata to functions using #[attribute] syntax. Common built-in attributes include:
Marks a function as a test case, runnable with nargo test:
#[test]
fn test_one() {
    assert(1 + 1 == 2);
}
Declares a function whose body is provided by the backend (a black-box function):
#[foreign(sha256)]
fn sha256(_input: [u8], _message_size: u32) -> [u8; 32] {}
Declares a function that is built into the compiler:
#[builtin(to_le_bits)]
fn to_le_bits(_x: Field, _bit_size: u32) -> [u1] {}
Any comptime function can be used as an attribute. The function receives the annotated item and can transform it:
#[my_function_attribute]
fn foo() {}

comptime fn my_function_attribute(f: FunctionDefinition) {
    println("Called my_function_attribute!");
}

Unconstrained functions

Unconstrained functions execute outside the constraint system. They are useful for computation that is too expensive to prove directly — for example, performing division by using a hint that is later verified:
fn main(x: u64, y: u64) {
    let div = unsafe { my_div(x, y) };
    // Verify the hint
    assert(div * y == x);
}

unconstrained fn my_div(x: u64, y: u64) -> u64 {
    x / y
}
Unconstrained code is not proven. It runs as a hint to generate witnesses, but the result must be explicitly verified with assertions in constrained code. Use unsafe { } blocks to call unconstrained functions from constrained contexts.
Unconstrained functions support additional control flow not available in constrained code:
  • loop / while loops
  • break and continue in loops
  • Dynamic-length operations
unconstrained fn count_down(start: u32) -> u32 {
    let mut i = start;
    while i > 0 {
        i -= 1;
    }
    i
}

Build docs developers (and LLMs) love