[newbie ?] Why macros vs. functions?

I am pretty new to system programming and rust. I was going through the Rust By Example and almost immediately (ok, immediately... heh) came across macros.

I get macros, I think, but I was left with "why?". Would a function not accomplish the same thing? Is a macro, because it expands to source instead of adding to the call graph, be faster?

I guess I am looking for a "why macros?" explanation.

cheers

  1. It expands to something that is typesafe at compile time. In C, printf tries to format things runtime, based on the format string. That isn't safe.
  2. It's very fast: it's possible to dispatch the formatters statically.
  3. It allows for variadic arguments, which functions don't currently have support for.

Edit: Oh, btw, I'm explaining specifically about println! macro here, as you can probably guess!

8 Likes

In addition to the above, macros allow you to encapsulate patterns that simply can't be done using a function abstraction. For example, the try! macro conditionally returns from the enclosing function. There's no way you could put this in a function, since the return would have to be non-local. Other macros allow you to declare types, functions, etc... that match a template. In both cases, the pattern being expressed is incompatible with the abstraction that functions provide.

3 Likes

Thanks to the prompt replies. I think I understand a bit more. I very much appreciate the time to help a noob.

@mackenza Here's a concrete example. The assert! macro looks like this:

#[macro_export]
#[stable(feature = "rust1", since = "1.0.0")]
macro_rules! assert {
    ($cond:expr) => (
        if !$cond {
            panic!(concat!("assertion failed: ", stringify!($cond)))
        }
    );
    ($cond:expr, $($arg:tt)+) => (
        if !$cond {
            panic!($($arg)+)
        }
    );
}

Why is assert a macro? There are multiple reasons but the clearest is this: stringify!($cond). This is doing magic that is not possible within the language itself. stringify! takes (more-or-less) the raw syntactic tokens and, at compile-time, produces their representation as a string literal. IOW stringify!(1 == 2) expands to the static string slice "1 == 2". This is what lets the assertion failure error message say what assertion failed.

Try to imagine how you would do that with a function:

fn assert(cond: bool) {
  // How do we recover what the expression was??
}
5 Likes