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.

Traits in Noir are analogous to interfaces or protocols in other languages. A trait defines a set of method signatures that a type must implement. This allows functions to be generic over any type that satisfies certain behavior, without needing to know the concrete type in advance.

Defining a trait

trait Area {
    fn area(self) -> Field;
}

Implementing a trait

Use impl TraitName for Type to provide concrete implementations:
struct Rectangle {
    width: Field,
    height: Field,
}

struct Triangle {
    width: Field,
    height: Field,
}

impl Area for Rectangle {
    fn area(self) -> Field {
        self.width * self.height
    }
}

impl Area for Triangle {
    fn area(self) -> Field {
        self.width * self.height / 2
    }
}

Using traits in generic functions

Add a where clause to constrain a generic type to implement a trait:
fn log_area<T>(shape: T) where T: Area {
    println(shape.area());
}
This is equivalent to the inline bound syntax:
fn log_area<T: Area>(shape: T) {
    println(shape.area());
}
Use where when there are many bounds and the separation improves readability.

Multiple bounds

Combine bounds with + and list multiple type constraints separated by commas:
fn foo<T, U>(elements: [T], thing: U) where
    T: Default + Add + Eq,
    U: Bar,
{
    let mut sum = T::default();

    for element in elements {
        sum += element;
    }

    if sum == T::default() {
        thing.bar();
    }
}

Invoking trait methods

To call a trait method on a concrete type, the trait must be imported into scope:
use geometry::Rectangle;
use geometry::Area; // must import the trait

fn main() {
    let rectangle = Rectangle { width: 1, height: 2 };
    let area = rectangle.area(); // ok: uses Area::area
}
If two traits in scope define the same method name, use the fully qualified path:
use geometry::Rectangle;

fn main() {
    let rectangle = Rectangle { width: 1, height: 2 };
    let area = geometry::Area::area(rectangle);
}

As-trait syntax

When a generic type must call a method from a specific trait (e.g. when two traits define the same method name), use <Type as Trait>::method:
trait Foo  { fn bar(); }
trait Foo2 { fn bar(); }

fn example<T>() where T: Foo + Foo2 {
    <T as Foo>::bar();
    <T as Foo2>::bar();
}

Default method implementations

A trait can provide a default body for any of its methods. Types only need to implement the required methods:
trait Numeric {
    fn add(self, other: Self) -> Self;

    // Default: double is implemented in terms of add
    fn double(self) -> Self {
        self.add(self)
    }
}

impl Numeric for Field {
    fn add(self, other: Field) -> Field {
        self + other
    }
    // double is inherited automatically
}

impl Numeric for u32 {
    fn add(self, other: u32) -> u32 {
        self + other
    }

    // Override the default
    fn double(self) -> u32 {
        self * 2
    }
}

Traits with no self parameter

Traits can define static methods (no self parameter). Call them via the type or the trait name:
trait Default {
    fn default() -> Self;
}

impl Default for Field {
    fn default() -> Field { 0 }
}

struct MyType {}

impl Default for MyType {
    fn default() -> MyType { MyType {} }
}

fn main() {
    let my_struct = MyType::default();
    let x: u64 = Default::default();
    let result = x + Default::default();
}

Generic trait implementations

Add generics after impl to implement a trait for a family of types:
trait Second {
    fn second(self) -> Field;
}

impl<T> Second for (T, Field) {
    fn second(self) -> Field {
        self.1
    }
}
You can implement a trait for all types:
trait Debug {
    fn debug(self);
}

impl<T> Debug for T {
    fn debug(self) {
        println(self);
    }
}

fn main() {
    1.debug();
}
Add where clauses on implementations to restrict which generics qualify:
impl<T, let N: u32> Eq for [T; N] where T: Eq {
    fn eq(self, other: Self) -> bool {
        let mut result = true;
        for i in 0..self.len() {
            result &= self[i] == other[i];
        }
        result
    }
}

Generic traits

Traits themselves can be generic. Specify the generic arguments when implementing or referencing the trait:
trait Into<T> {
    fn into(self) -> T;
}

struct MyStruct {
    array: [Field; 2],
}

impl Into<[Field; 2]> for MyStruct {
    fn into(self) -> [Field; 2] {
        self.array
    }
}

fn as_array<T>(x: T) -> [Field; 2] where T: Into<[Field; 2]> {
    x.into()
}

fn main() {
    let array = [1, 2];
    let my_struct = MyStruct { array };
    assert_eq(as_array(my_struct), array);
}

Associated types and constants

Traits support associated types (type Foo) and associated constants (let Bar: u32):
trait MyTrait {
    type Foo;
    let Bar: u32;
}

impl MyTrait for Field {
    type Foo = i32;
    let Bar: u32 = 11;
}

// Specify associated items explicitly
fn foo<T>(x: T) where T: MyTrait<Foo = i32, Bar = 11> { ... }

// Or let the compiler infer them
fn bar<T>(x: T) where T: MyTrait { ... }

// Reference an associated type via as-trait syntax
fn baz<T>(x: T) where
    T: MyTrait,
    <T as MyTrait>::Foo: Default + Eq
{
    let foo_value: <T as MyTrait>::Foo = Default::default();
    assert_eq(foo_value, foo_value);
}

Impl specialization

Implement a trait for only a specific combination of generics:
trait Sub {
    fn sub(self, other: Self) -> Self;
}

struct NonZero<T> {
    value: T,
}

impl Sub for NonZero<Field> {
    fn sub(self, other: Self) -> Self {
        let value = self.value - other.value;
        assert(value != 0);
        NonZero { value }
    }
}

Overlapping implementations

Overlapping impl blocks are disallowed to ensure unambiguous dispatch:
trait Trait {}

impl<A, B> Trait for (A, B) {}

// error: overlapping impl for (Field, Field)
// impl Trait for (Field, Field) {}

Trait coherence

To implement a trait, either the trait or the implementing type must be defined in the current crate. This prevents conflicts when using external libraries. If you need to implement a foreign trait on a foreign type, use the newtype pattern:
struct Wrapper {
    foo: some_library::Foo,
}

impl Default for Wrapper {
    fn default() -> Wrapper {
        Wrapper {
            foo: some_library::Foo::new(),
        }
    }
}

Trait inheritance

A trait can require that implementors also implement other traits (supertraits):
trait Person {
    fn name(self) -> String;
}

// Student requires Person to also be implemented
trait Student: Person {
    fn university(self) -> String;
}

trait Programmer {
    fn fav_language(self) -> String;
}

// CompSciStudent requires both Programmer and Student
trait CompSciStudent: Programmer + Student {
    fn git_username(self) -> String;
}

Trait aliases

Trait aliases combine multiple traits under a single name:
trait Foo {
    fn foo(self) -> Self;
}

trait Bar {
    fn bar(self) -> Self;
}

trait Baz = Foo + Bar;

fn baz<T>(x: T) -> T where T: Baz {
    x.foo().bar()
}
Generic trait aliases and where clauses are supported:
trait Bar<T> {
    fn bar(self) -> T;
}

trait Baz<T> = Foo + Bar<T>;

trait Baz {
    fn baz(self) -> bool;
}

// Qux<T> requires Foo + Bar<T> where T: Baz
trait Qux<T> = Foo + Bar<T> where T: Baz;

Visibility

Traits and trait aliases are private to their module by default. Use pub or pub(crate) to expose them:
pub trait Trait {}
pub trait Baz = Foo + Bar;

Operator overloading

Noir’s arithmetic and comparison operators are implemented via standard library traits. Implement these traits on your types to enable operator syntax:
Implement Add, Sub, Mul, Div, Rem from std::ops:
use std::ops::Add;

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

impl Add for Vec2 {
    fn add(self, other: Vec2) -> Vec2 {
        Vec2 { x: self.x + other.x, y: self.y + other.y }
    }
}

fn main() {
    let a = Vec2 { x: 1, y: 2 };
    let b = Vec2 { x: 3, y: 4 };
    let c = a + b; // uses Add::add
    assert(c.x == 4);
    assert(c.y == 6);
}

Build docs developers (and LLMs) love