user2455742
user2455742

Reputation: 31

How to capture by reference in rust macro

I have a macro to generate match arms:

macro_rules! sort_by {
    ( $query:ident, $sort_by:expr, { $( $name:pat => $column:path,)+ } ) => {
        match $sort_by.column {
            $(
                $name => if $sort_by.descending {
                    $query = $query.order_by($column.desc());
                } else {
                    $query = $query.order_by($column.asc());
                },
            )+
        }
    }
}

and I want to call it like this:

sort_by!(query, sort_by.unwrap_or(Sort::desc("id")), {
    "id" => table::id,
    "customerName" => table::customer_name,
});

But I'm getting an error:

sort_by!(query, &sort_by.unwrap_or(Sort::desc("id")), {
                ^^^^^^^ value moved here in previous iteration of loop

So I have to call it like this:

let sort = sort_by.unwrap_or(Sort::desc("id"));
sort_by!(query, &sort, {
    "id" => table::id,
    "customerName" => table::customer_name,
});

What should I change to be able to use the expression directly in the macro invocation?

Upvotes: 2

Views: 1714

Answers (1)

Sven Marnach
Sven Marnach

Reputation: 602685

Using a macro is equivalent to substituting the code it expands to into its call site. This means if the macro expansion contains $sort_by multiple times, the code will evaluate the expression you pass in as $sort_by multiple times. If the expression consumes some variable, this will be invalid.

This is in contrast to how function calls work. If you pass an expression to a function, it will be evaluated before calling the function, and only the result is passed to the function.

If this is the source of your problem, you can fix it by assigning $sort_by to a local variable inside your macro expansion, and only access the local variable subsequently:

macro_rules! sort_by {
    ($query:ident, $sort_by:expr, { $($name:pat => $column:path,)+ }) => {
        let sort_by = $sort_by;
        match sort_by.column {
            $(
                $name => if sort_by.descending {
                    $query = $query.order_by($column.desc());
                } else {
                    $query = $query.order_by($column.asc());
                },
            )+
        }
    }
}

(Note that I could not test this, since your example is incomplete.)

Upvotes: 3

Related Questions