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.

Metaprogramming in Noir is built on three pillars:
  1. comptime code — functions, globals, blocks, and loops that execute at compile time.
  2. Quoting and unquoting — creating, manipulating, and splicing token streams (Quoted values).
  3. The std::meta API — compiler-provided types and intrinsics that let you inspect and modify the program.
Together these allow you to write macros: comptime functions that generate and insert code into the program.

comptime basics

The comptime keyword marks code as executing at compile time:
SyntaxMeaning
comptime fnFunction that runs only during compilation
comptime globalGlobal variable evaluated at compile time (may be mutable)
comptime letVariable whose value is evaluated at compile time
comptime { ... }Block of statements executed at compile time
comptime forfor loop executed at compile time

comptime fn

comptime fn get_type() -> Type { ... }
Comptime functions can be called at compile time and can return values like Quoted, Type, FunctionDefinition, etc.

comptime global

Unlike runtime globals, comptime global values can be mutable:
comptime global mut COUNTER: u32 = 0;

comptime let

fn main() {
    comptime let x = 1 + 2; // evaluated at compile time
    println(x);              // x is lowered to the literal 3
}

comptime blocks

A comptime {} block runs its statements during compilation. The result is lowered to a runtime literal:
struct Foo { array: [Field; 2], len: u32 }

fn main() {
    println(comptime {
        let mut foo = std::mem::zeroed::<Foo>();
        foo.array[0] = 4;
        foo.len = 1;
        foo
    });
}
After evaluation, the above is equivalent to:
fn main() {
    println(Foo { array: [4, 0], len: 1 });
}

comptime for

Syntactic sugar for comptime { for ... }. Runs a for loop at compile time:
comptime for i in 0..5 {
    // executed 5 times during compilation
}

Quote and unquote

A Quoted value is a token stream — a compile-time representation of source code. Create one with a quote { ... } expression:
comptime {
    let q = quote { x + 1 };
    // q is the token stream [x, +, 1]
}
Square brackets also work, which is useful for quoting mismatched curly braces:
comptime {
    let q1 = quote { 1 };
    let q2 = quote [ 2 ];
    assert_eq(q1, q2); // both are single-token streams

    // Square brackets allow quoting a lone }
    let _ = quote[}];
}

Unquoting with $

The $ operator splices a variable’s value into a quoted token stream:
comptime {
    let x = 1 + 2;
    let y = quote { $x + 4 };
    // y is [3, +, 4]
}
Combine Quoted values:
comptime {
    let x = quote { 1 + 2 };
    let y = quote { $x + 4 };
    // y is [1, +, 2, +, 4]
}
Escape $ with a backslash to prevent unquoting:
comptime {
    let x = quote { 1 + 2 };
    let y = quote { \$x + 4 };
    // y contains the literal tokens [$, x, +, 4]
}
Quoted is internally a vector of tokens, not a string. Concatenation works like vector concatenation. foo$x with x = 3 gives [foo, 3], not [foo3]. Use format strings and .quoted_contents() if you need string semantics.

$crate

When writing macros in a library, use $crate to refer to the crate the quote is in, ensuring it resolves correctly even when the macro is used from external crates:
pub fn double(x: u64) -> u64 { x * 2 }

comptime fn double_twice(code: Quoted) -> Quoted {
    quote {
        $crate::double($crate::double($code))
    }
}

Calling macros

A comptime function that returns Quoted is a macro. Append ! to the function call to insert the token stream at the call site:
comptime fn my_macro() -> Quoted {
    quote { 1 + 1 }
}

fn main() {
    let x = my_macro!(); // expands to: let x = 1 + 1;
}
Without !, the function just returns the Quoted value for further manipulation.

Attributes

An attribute applies a comptime function to an item. The function is called with that item as its first argument:
#[my_struct_attribute]
struct Foo {}

comptime fn my_struct_attribute(s: TypeDefinition) {
    println("Called on Foo!");
}

#[my_function_attribute]
fn foo() {}

comptime fn my_function_attribute(f: FunctionDefinition) {
    println("Called on foo!");
}
Anything the attribute function returns is inserted at the top level alongside the original item. This is how #[derive]-like functionality works.

Attribute arguments

Pass additional arguments to an attribute:
#[my_attr(42)]
struct Foo {}

comptime fn my_attr(s: TypeDefinition, value: Field) {
    println(f"Called with value {value}");
}

Attribute evaluation order

Within a module, attributes are evaluated top to bottom. Attributes in child modules are evaluated before those in parent modules. Sibling modules are evaluated in the order of their mod declarations:
mod foo; // attributes in foo evaluated first
mod bar; // then bar

#[derive(Eq)]
struct Baz {} // then parent module attributes
Avoid deriving a trait for a struct whose fields haven’t had the trait derived yet — the attribute will fail because the required impl doesn’t exist yet. Reorder your structs (innermost first) to fix this.

The std::meta API

The std::meta module provides compiler-backed types for inspecting and modifying the program. Key types include:
A token stream. Methods include:
  • .tokens() — iterate over individual tokens
  • .quoted_contents() — convert a format string back to Quoted
  • .as_type() — parse as a type in the current scope
  • .as_trait_constraint() — parse as a trait constraint
Represents a Noir type at compile time.
  • fn implements(self, constraint: TraitConstraint) -> bool — check if this type implements a trait
A syntactically valid expression. Useful for recursing over the parse tree.
  • fn as_function_call(self) -> Option<(Expr, [Expr])> — if this is a function call, return (function, arguments)
  • fn as_block(self) -> Option<[Expr]> — if this is a block, return the statements
Represents a function.
  • fn parameters(self) -> [(Quoted, Type)] — returns (name, type) pairs for each parameter
Represents a struct or enum definition.
  • fn as_type(self) -> Type — returns this as a Type
  • fn generics(self) -> [Quoted] — returns names of each generic
  • fn fields(self) -> [(Quoted, Type)] — returns name and type of each field
A trait constraint expression such as From<Field>.

#[use_callers_scope]

By default, Quoted::as_type and similar methods resolve in the attribute function’s scope. If you want them to resolve in the scope of the caller (the code being annotated), add #[use_callers_scope] to your attribute function:
#[use_callers_scope]
comptime fn my_attr(f: FunctionDefinition) {
    // Can now refer to types visible at the call site
}

Writing a derive macro

The full metaprogramming system enables derive-like functionality. From the user’s perspective:
#[derive(Default, Eq, Ord)]
struct MyStruct { my_field: u32 }
Implementing derive requires a comptime function that:
  1. Accepts the struct’s TypeDefinition.
  2. Iterates over trait names passed as arguments.
  3. Looks up a registered derive function for each trait.
  4. Calls it and collects the generated Quoted impls.
// Example: registering a custom derive handler
comptime fn derive_my_trait(s: TypeDefinition) -> Quoted {
    let type_name = s.as_type();
    let fields = s.fields();
    // build and return the impl as a Quoted token stream
    quote {
        impl MyTrait for $type_name {
            // ...
        }
    }
}
The returned Quoted value is inserted at the top level, adding a new trait implementation to the program.

Counting fields with a derive attribute

A simple derive example that counts struct fields at compile time and generates a field_count method:
#[derive_field_count]
struct Foo { x: Field, y: Field }

comptime fn derive_field_count(s: TypeDefinition) -> Quoted {
    let typ = s.as_type();
    let field_count = s.fields().len();
    quote {
        impl $typ {
            fn field_count() -> u32 { $field_count }
        }
    }
}
After compilation, Foo::field_count() returns 2.

Build docs developers (and LLMs) love