Macros and Ownership

Why does the println!() macro NOT take ownership of variables you pass into it?
For instance

let my_string = String::from("hello world!");
println!("My string is {} ", my_string);
/*my_string is still in scope here 
even though it isn't `&my_string` that is passed to the macro*/

Aren't variables usually borrowed when you want the calling function to retain ownership?
I have a slight idea that macros expand in place (instead of control jumps like in functions) during compilation. Does it have anything to do with this behaviour?

It totally depends on how the macros uses the identifier passed in.
println! uses it as &ident, so there's a hidden borrow.
dbg! uses it as ident, so it will consume the variable.

4 Likes

It's as simple as this really:

macro_rules! do_thing {
    ($arg:expr) => {
        let arg = &$arg;
        // use arg immutably here
    } 
} 

That way you can toss in whatever and it'll work.
The main difference with println is that it handles any number of such args, so the pattern recognized by println! and friends is slightly more complex.

2 Likes

No. Macros aren't functions, they don't work like functions at all, and they don't even interact with the type system in any meaningful way (they are expanded way before type checking even begins), so they don't know about things like "borrowing" and "ownership".

A macro operates at a purely syntactic level. It takes some raw-ish source code, transforms it, and emits some other, raw source code. It doesn't know about types, it doesn't care about functions, all it cares about is that parentheses are balanced (because macros in Rust don't actually operate on the source as a string, but on a pre-lexed representation called a "token stream" or "token tree").

So the only reason why println!() doesn't move arguments passed to it is that it expands to (emits) code that borrows the arguments. It could as well be implemented in a way that it moves its arguments, but that would be very inconvenient and impractical. Anyway, here is such an implementation:

macro_rules! moving_println {
    () => { println!() };
    ($fmt:expr $(, $name:ident = $value:expr)*) => {{
        $(let $name = $value;)*
        println!($fmt, $($name = $name,)*);
    }};
}
3 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.