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.

Generics allow you to write functions and data structures that work across multiple concrete types. By convention, Noir uses T for a general type parameter, following Rust’s conventions.

Generic functions

The identity function accepts any type and returns it unchanged:
fn id<T>(x: T) -> T {
    x
}
Generic type parameters are listed in angle brackets after the function name. The compiler infers the concrete type at each call site.

Generic structs

Structs can be generic over one or more type parameters:
struct RepeatedValue<T> {
    value: T,
    count: u32,
}

impl<T> RepeatedValue<T> {
    fn print(self) {
        for _i in 0..self.count {
            println(self.value);
        }
    }
}

fn main() {
    let repeated = RepeatedValue { value: "Hello!", count: 2 };
    repeated.print(); // prints "Hello!" twice
}

Numeric generics

Numeric generics allow you to be generic over compile-time integer values — most commonly array lengths. Declare them with let Name: IntegerType:
struct BigInt<let N: u32> {
    limbs: [u32; N],
}

impl<let N: u32> BigInt<N> {
    fn first(first: BigInt<N>, second: BigInt<N>) -> Self {
        assert(first.limbs != second.limbs);
        first
    }

    fn second(first: BigInt<N>, second: Self) -> Self {
        assert(first.limbs != second.limbs);
        second
    }
}
N is in scope for all methods in the impl block.
When passing numeric generic arguments, integer literals default to u32. If the generic requires a different type, you must use an integer suffix:
fn foo<let A: u8, let B: u32, let C: i64>() {}

fn main() {
    foo::<0u8, 2, 3i64>();
}

Trait bounds on generics

A generic type T represents any type. To call methods on T, constrain it with a trait bound:
fn first_element_is_equal<T, let N: u32>(array1: [T; N], array2: [T; N]) -> bool
    where T: Eq
{
    if (array1.len() == 0) | (array2.len() == 0) {
        true
    } else {
        array1[0] == array2[0]
    }
}

fn main() {
    assert(first_element_is_equal([1, 2, 3], [1, 5, 6]));

    let array = [MyStruct::new(), MyStruct::new()];
    assert(first_element_is_equal(array, array));
}

struct MyStruct { foo: Field }

impl MyStruct {
    fn new() -> Self { MyStruct { foo: 0 } }
}

impl Eq for MyStruct {
    fn eq(self, other: MyStruct) -> bool {
        self.foo == other.foo
    }
}
See Traits for full details on trait bounds and where clauses.

The turbofish operator

When the compiler cannot infer a generic type, use the ::<> turbofish operator to specify it explicitly:
fn main() {
    let mut vector = [];
    vector = vector.push_back(1);
    vector = vector.push_back(2);
    // Specify the result array length explicitly
    let array = vector.as_array::<2>();
}
The turbofish operator also works on method calls:
trait MyTrait {
    fn ten() -> Self;
}

impl MyTrait for Field {
    fn ten() -> Self { 10 }
}

struct Foo<T> {
    inner: T
}

impl<T> Foo<T> {
    fn generic_method<U>(_self: Self) -> U where U: MyTrait {
        U::ten()
    }
}

fn example() {
    let foo: Foo<Field> = Foo { inner: 1 };
    assert(10 == foo.generic_method::<Field>());
}

Arithmetic generics

Noir allows limited arithmetic on numeric generics within type positions. Supported operators are +, -, *, /, and %:
trait Serialize<let N: u32> {
    fn serialize(self) -> [Field; N];
}

impl Serialize<1> for Field {
    fn serialize(self) -> [Field; 1] {
        [self]
    }
}

impl<T, let N: u32, let M: u32> Serialize<N * M> for [T; N]
    where T: Serialize<M> { .. }

impl<T, U, let N: u32, let M: u32> Serialize<N + M> for (T, U)
    where T: Serialize<N>, U: Serialize<M> { .. }

fn main() {
    let data = (1, [2, 3, 4]);
    assert_eq(data.serialize().len(), 4);
}
Arithmetic generic type checking is a best-effort operation. The compiler does not apply algebraic laws (e.g. distributivity), so T * (N + M) and T*N + T*M are treated as distinct types even if they are equal mathematically. Overflow or underflow in generic arithmetic will cause types to fail to unify.

Numeric type aliases

Type aliases can also be defined for numeric types, which helps cut down on long type expressions:
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
}
When used inside a non-array type position, the turbofish :: is not needed:
struct Array<T, let N: u32> {
    data: [T; N],
}

fn concat_self2<let N: u32>(array: Array<u32, N>) -> Array<u32, Double<N>> {
    Array { data: concat_self(array.data) }
}

Build docs developers (and LLMs) love