---
title: "Move 2024: Macro Functions Guide"
description: "Move 2024 beta edition now gives developers the ability to replace common loops with macro functions."
author: "Sam Blackshear"
datePublished: 2024-08-28
dateModified: 2026-07-30
category: "Developer Resources"
readTime: "11 min"
canonical: https://www.sui.io/blog/move-2024-macros-beta
---

# Move 2024: Macro Functions Guide

Move 2024 beta now includes `macro` functions! 

Macro functions in Move are similar to regular functions: you define the parameters, return type, and logic that determines how your macro functions operate, and then you can then call your macro functions from anywhere in your code. Unlike regular functions, however, the compiler _expands_ those functions in place to execute the defined operations inline.

The main differences that separate macro functions from normal functions are: 

-   Macro function syntax extensions
-   Move compiler expansion and call semantics
-   Lambda parameters

**Syntax extensions:** Move syntax specifies how to define macro functions, their arguments, and their invocation. These rules not only help the compiler differentiate macros from normal functions, but also provide a visual differentiation between normal and macro functions during usage.

**Compiler expansion and call semantics:** Move eagerly evaluates normal function arguments. In other words, the runtime fully evaluates the arguments you pass to a normal function, whether necessary to the function logic or not. In contrast, macro functions directly substitute the expressions for their arguments during expansion, and the runtime won’t see the macro call at all! This means that arguments aren’t evaluated at all if the macro body code with the argument is never reached. For example, if the argument is substituted in an if-else expression and the branch with that argument is never taken, the argument will not be evaluated.

**Lambda parameters:** In addition to normal expressions, Macro functions allow you to supply parameterized code as higher-order function arguments. This is made possible with the introduction of the lambda type, allowing macro authors to create powerful and flexible macros. 

For more details on how to define macro functions, see the official [reference in the Move book](https://move-book.com/reference/functions/macros.html).

## Anatomy of macro functions

The Move compiler expands macro functions during compilation, enabling expressiveness not available in normal functions. To indicate this special substitution behavior, macro functions have their type parameters and expression parameters prefixed with a `$` character. Consider a Move function titled \`my\_assert\` that behaves like the compiler primitive `assert!` (without clever errors). 

`macro fun my_assert($cond: bool, $code: u64) {       if (!$cond) abort $code.   }`

The Move compiler substitutes the arguments to type parameters and expression parameters during expansion. This behavior creates the lazy evaluation process for arguments mentioned in the previous section. As the code shows, the \`my\_assert\` macro checks the condition passed to it, aborting the program if the condition \`$cond\` resolves to `false` (or not \`true\`) with the \`$code\` passed as a second argument. 

To invoke the `my_assert` macro function, the programmer uses a `!` character after the function name to visually indicate that the arguments are not eagerly evaluated:

`my_assert!(vector[] == vector[], 0);   my_assert!(true, 1 / 0); // will not abort! '1 / 0' is not evaluated.`

As you might notice, the `$code` value passed to the macro in the second invocation includes a division by zero operation. Although a bit contrived, its purpose is to show that the compiler will not evaluate the expression because control flow will never reach its execution due to the provided `true` condition.     

### Macros with lambda arguments

The addition of macro functions includes the introduction of an entirely new feature: lambda. Using parameters with lambda types, you can pass code from the caller into the body of the macro. While the substitution is done at compile time, they are used similarly to [anonymous functions](https://en.wikipedia.org/wiki/Anonymous_function), [lambdas](https://en.wikipedia.org/wiki/Lambda_calculus), or [closures](https://en.wikipedia.org/wiki/Closure_\(computer_programming\)) in other languages.

For example, consider a loop that executes a lambda on each number from `0` to a number `n` that the code provides.

`public macro fun do($n: u64, $f: |u64| -> ()) {       let mut i = 0;       let stop = $n;       while (i < stop) {           $f(i);           i = i + 1;       }   }`

The lambda parameter (`$f`) is the second argument to the `do` macro. The lambda’s type is defined as `|u64| -> ()`, which takes a `u64` as input and returns `()`. The return type of `()` is implicit, so the argument could also be written simply as `$f: |u64|`. 

Your program logic could then use this macro to sum the numbers from `0` to `10` using the lambda parameter:

`let mut sum = 0;   do!(10, |i| sum = sum + i);`

Here, the code snippet \`|i| sum = sum + i\` defines the lambda for the macro invocation. When it is called in the macro’s body, its arguments are evaluated and bound in the body of the lambda. Note that, unlike macro calls, lambdas will completely evaluate their arguments before executing the lambda body.

> **Macro functions are hygenic, so the i in the lambda is not the same as the i in the do.** [**See the Move reference for more details**](https://move-book.com/reference/functions/macros.html#hygiene)**.)**

## Macros in the Move standard library

With the addition of macros, the `move-stdlib` library introduces a number of macro functions to capture common programming patterns.

### Integer macros

For `u64` and other integer types, the `move-stdlib` library provides a set of macro functions that simplify common loops. This post examines the `u64` type, but the same macro functions are available for `u8`, `u16`, `u32`, `u128`, and `u256`. 

> **You have already seen one example, as the `do` function code is a macro function in `std::u64`.** 

The full list of currently available macro functions for integer types are:

-   `public macro fun do($stop: u64, $f: |u64|)` Loops applying `$f` to each number from `0` to `$stop` (exclusive).
-   `public macro fun do_eq($stop: u64, $f: |u64|)` Loops applying `$f` to each number from `0` to `$stop` (inclusive).
-   `public macro fun range_do($start: u64, $stop: u64, $f: |u64|)` Loops applying `$f` to each number from `$start` to `$stop` (exclusive).
-   `public macro fun range_do_eq($start: u64, $stop: u64, $f: |u64|)` Loops applying `$f` to each number from `$start` to `$stop` (inclusive).

For a simplified example of applying the defined macro functions, consider the following loop:

`let mut i = 0;   while (i < 10) {       foo(i);       i = i + 1;   }`

You could rewrite this loop by implementing the `do` macro function for `std::u64` as:

`10u64.do!(|i| foo(i));`

Similarly, you could make the following loop more concise using the `u64::range_do_eq macro function`:

`let mut i = 1;   // loop from 1 to 10^8 by 10s   while (i <= 100_000_000) {       foo(i);       i = i * 10;   }`

The result of the rewrite:

`1u64.range_do_eq!(8, |i| foo(10u64.pow(i)));`

> **While this is potentially easier to read, it will take more gas to execute.**

### `vector` macros

In a similar spirit to integers' `do` macro, you can iterate over the elements of a vector using the `do` function.

`vector[1, 2, 3, 4, 5].do!(|x| foo(x));`

This is equivalent to:

`let mut v = vector[1, 2, 3, 4, 5];   v.reverse();   while (!v.is_empty()) {       foo(v.pop_back());   }`

The expanded code shows that this process consumes the vector. Use the `do_ref` and `do_mut` macros to iterate by reference or by mutable reference, respectively.

`fun check_coins(coins: &vector<Coin<SUI>>) {       coins.do_ref!(|coin| assert!(coin.value() > 0));       /* expands to       let mut i = 0;       let n = coins.len();       while (i < n) {           let coin = &coins[i];           assert!(coin.value() > 0);           i = i + 1;       }       */   }   fun take_10(coins: &mut vector<Coin<SUI>>) {       coins.do_mut!(|coin| transfer::public_transfer(coin.take(10), @0x42));       /* expands to       let mut i = 0;       let n = coins.len();       while (i < n) {           let coin = &mut coins[i];           transfer::public_transfer(coin.take(10), @0x42);           i = i + 1;       }       */   }`

In addition to iteration, you can modify and create vectors with macros. `vector::tabulate` creates a vector of length `n` by applying a lambda to each index.

`fun powers_of_2(n: u64): vector<u64> {       vector::tabulate(n, |i| 2u64.pow(i))       /* expands to       let mut v = vector[];       let mut i = 0;       while (i < n) {           v.push_back(2u64.pow(i));           i = i + 1;       };       v       */   }`

`vector::map` creates a new vector from an existing vector by applying a lambda to each element. While `vector::map` operates on a vector by value, the `map_ref` and `map_mut` macros operate on a vector by reference and mutable reference, respectively.

`fun into_balances(coins: vector<Coin<SUI>>): vector<Balance<SUI>> {       coins.map!(|coin| coin.into_balance())       /* expands to       let mut v = vector[];       coins.reverse();       while (!coins.is_empty()) {           let coin = coins.pop_back();           v.push_back(coin.into_balance());       };       v       */   }`

Similar to map, `vector::filter` creates a new vector from an existing vector by keeping only elements that satisfy a predicate.

`fun non_zero_numbers(numbers: vector<u64>): vector<u64> {       numbers.filter!(|n| n > 0)       /* expands to       let mut v = vector[];       numbers.reverse();       while (!numbers.is_empty()) {           let n = numbers.pop_back();           if (n > 0) {               v.push_back(n);           }       };       v       */   }`

> **See Table A at the end of this article for a list of currently available macro functions for vectors.**

## `option` macros

While `Option` is not a collection with more than one element, the type has `do` (and `do_ref` and `do_mut`) macros to easily access its value if it is `some`.

`fun maybe_transfer(coin: Option<Coin<SUI>>, recipient: address) {       coin.do!(|c| transfer::public_transfer(c, recipient))       /* expands to       if (coin.is_some()) transfer::public_transfer(coin.destroy_some(), recipient)       else coin.destroy_none()       */   }`

`destroy` performs the same action as `do`, but perhaps a more useful `macro` is `destroy_or` which provides a default expression, which is evaluated only if the `Option` is `none`. The following example chains `map`, which applies the lambda to the value of the `Option` if it is `some`, and `destroy_or` to concisely convert an `Option<Coin<SUI>>` to a `Balance<SUI>`.

`fun to_balance_opt(coin: Option<Coin<SUI>>): Balance<SUI> {       coin.map!(|c| c.into_balance()).destroy_or!(Balance::zero())       /* expands to       let opt = if (coin.is_some()) Option::some(coins.destroy_some().into_balance())                 else Option::none();       if (opt.is_some()) opt.destroy_some()       else Balance::zero()       */   }`

> **See Table B at the end of this article for a list of currently available macro functions for `Option`.**

## Wrap up

Macro functions can simplify your code by converting common patterns into reusable macros. The hope is that macro functions can replace many, if not all, of your common loops (loop and while). Keep an eye out for more macro functions in the move-stdlib library, and soon the sui framework.

For more details on how macro functions work, see the [Move book](https://move-book.com/reference/functions/macros.html).

### Table A: Currently available macro functions for vectors

Prepend `public macro fun` to each line below:

  

`tabulate<$T>($n: u64, $f: |u64| -> $T): vector<$T>`

Create a vector of length `n` by calling the function `f` on each index.

`destroy<$T>($v: vector<$T>, $f: |$T|)`

Destroy the vector `v` by calling `f` on each element and then destroying the vector. Does not preserve the order of elements in the vector (starts from the end of the vector).

`do<$T>($v: vector<$T>, $f: |$T|)`

Destroy the vector `v` by calling `f` on each element and then destroying the vector. Preserves the order of elements in the vector.

`do_ref<$T>($v: &vector<$T>, $f: |&$T|)`

Perform an action `f` on each element of the vector `v`. The vector is not modified.

`do_mut<$T>($v: &mut vector<$T>, $f: |&mut $T|)`

Perform an action `f` on each element of the vector `v`. The function `f/ takes a mutable reference to the element.`

`map<$T, $U>($v: vector<$T>, $f: |$T| -> $U): vector<$U>`

Map the vector `v` to a new vector by applying the function `f` to each element. Preserves the order of elements in the vector, first is called first.

`map_ref<$T, $U>($v: &vector<$T>, $f: |&$T| -> $U): vector<$U>`

Map the vector `v` to a new vector by applying the function `f` to each element. Preserves the order of elements in the vector, first is called first.

`filter<$T: drop>($v: vector<$T>, $f: |&$T| -> bool): vector<$T>`

Filter the vector `v` by applying the function `f` to each element. Return a new vector containing only the elements for which `f` returns `true`.

`partition<$T>($v: vector<$T>, $f: |&$T| -> bool): (vector<$T>, vector<$T>)`

Split the vector `v` into two vectors by applying the function `f` to each element. Return a tuple containing two vectors: the first containing the elements for which `f` returns `true`, and the second containing the elements for which `f` returns `false`.

`find_index<$T>($v: &vector<$T>, $f: |&$T| -> bool): Option<u64>`

Finds the index of first element in the vector `v` that satisfies the predicate `f`. Returns `some(index)` if such an element is found, otherwise `none().`

`count<$T>($v: &vector<$T>, $f: |&$T| -> bool): u64`

Count how many elements in the vector `v` satisfy the predicate `f.`

`fold<$T, $Acc>($v: vector<$T>, $init: $Acc, $f: |$Acc, $T| -> $Acc): $Acc`

Reduce the vector `v` to a single value by applying the function `f` to each element. Similar to `fold_left` in Rust and `reduce` in Python and JavaScript.

`any<$T>($v: &vector<$T>, $f: |&$T| -> bool): bool`

Whether any element in the vector `v` satisfies the predicate `f`. If the vector is empty, returns `false.`

`all<$T>($v: &vector<$T>, $f: |&$T| -> bool): bool`

Whether all elements in the vector `v` satisfy the predicate `f`. If the vector is empty, returns `true.`

`zip_do<$T1, $T2>($v1: vector<$T1>, $v2: vector<$T2>, $f: |$T1, $T2|)`

Destroys two vectors `v1` `and` `v2` by calling `f` to each pair of elements. Aborts if the vectors are not of the same length. The order of elements in the vectors is preserved.

`zip_do_reverse<$T1, $T2>($v1: vector<$T1>, $v2: vector<$T2>, $f: |$T1, $T2|)`

Destroys two vectors `v1` `and` `v2` by calling `f` to each pair of elements. Aborts if the vectors are not of the same length. Starts from the end of the vectors.

`zip_do_ref<$T1, $T2>($v1: &vector<$T1>, $v2: &vector<$T2>, $f: |&$T1, &$T2|)`

Iterate through `v1` `and` `v2` and apply the function `f` to references of each pair of elements. The vectors are not modified. Aborts if the vectors are not of the same length. The order of elements in the vectors is preserved.

`zip_do_mut<$T1, $T2>($v1: &mut vector<$T1>, $v2: &mut vector<$T2>, $f: |&mut $T1, &mut $T2|)`

Iterate through `v1` and `v2` and apply the function `f` to mutable references of each pair of elements. The vectors may be modified. Aborts if the vectors are not of the same length. The order of elements in the vectors is preserved.

`zip_map<$T1, $T2, $U>($v1: vector<$T1>, $v2: vector<$T2>, $f: |$T1, $T2| -> $U): vector<$U>`

Destroys two vectors `v1` `and` `v2` by applying the function `f` to each pair of elements. The returned values are collected into a new vector. Aborts if the vectors are not of the same length. The order of elements in the vectors is preserved.

`zip_map_ref<$T1, $T2, $U>($v1: &vector<$T1>, $v2: &vector<$T2>, $f: |&$T1, &$T2| -> $U): vector<$U>` 

Iterate through `v1` and `v2` and apply the function `f` to references of each pair of elements. The returned values are collected into a new vector. Aborts if the vectors are not of the same length. The order of elements in the vectors is preserved.

### Table B: Currently available macro functions for `Option`

Prepend `public macro fun` to each line below:

  

`destroy<$T>($o: Option<$T>, $f: |$T|)` 

Destroy `Option<T>` and call the closure `f` on the value inside if it holds one.

`do<$T>($o: Option<$T>, $f: |$T|)` 

Destroy `Option<T>` and call the closure `f` on the value inside if it holds one.

`do_ref<$T>($o: &Option<$T>, $f: |&$T|)` 

Execute a closure on the value inside `t` if it holds one.

`do_mut<$T>($o: &mut Option<$T>, $f: |&mut $T|)`

Execute a closure on the mutable reference to the value inside `t` if it holds one.

`or<$T>($o: Option<$T>, $default: Option<$T>): Option<$T>` 

Select the first `Some` value from the two options, or `None` if both are `None`. Equivalent to Rust's `a.or(b)`.

`and<$T, $U>($o: Option<$T>, $f: |$T| -> Option<$U>): Option<$U>` 

If the value is `Some`, call the closure `f` on it. Otherwise, return `None`. Equivalent to Rust's `t.and_then(f)`.

`and_ref<$T, $U>($o: &Option<$T>, $f: |&$T| -> Option<$U>): Option<$U>`

If the value is `Some`, call the closure `f` on it. Otherwise, return `None`. Equivalent to Rust's `t.and_then(f)`.

`map<$T, $U>($o: Option<$T>, $f: |$T| -> $U): Option<$U>` 

Map an `Option<T>` to `Option<U>` by applying a function to a contained value. Equivalent to Rust's `t.map(f)`.

`map_ref<$T, $U>($o: &Option<$T>, $f: |&$T| -> $U): Option<$U>`

Map an `Option<T>` value to `Option<U>` by applying a function to a contained value by reference. Original `Option<T>` is preserved. Equivalent to Rust's `t.map(f)`.

`filter<$T: drop>($o: Option<$T>, $f: |&$T| -> bool): Option<$T>`

Return `None` if the value is `None`, otherwise return `Option<T>` if the predicate `f` returns true.

`is_some_and<$T>($o: &Option<$T>, $f: |&$T| -> bool): bool`

Return `false` if the value is `None`, otherwise return the result of the predicate `f`.

`destroy_or<$T>($o: Option<$T>, $default: $T): $T`

Destroy `Option<T>` and return the value inside if it holds one, or `default` otherwise. Equivalent to Rust's `t.unwrap_or(default)`.

  

Note: this function is a more efficient version of `destroy_with_default`, as it does not evaluate the default value unless necessary. The `destroy_with_default` function should be deprecated in favor of this function.
