Conventions: macro vs function

So, since Rust is a language a lot about discipline and I'm just interested in what is the better convention, when being able to solve a small repetitive code, is there a preference of macro vs function. where both can be done. Here, I'm aware that macros can do magic ,which functions can't, but not always we need that. So for instance a bollocks code example (just example) would be smth coming in a single module:

macro_rules! write_node{
	($fmt: ident, $node: expr, $cl: expr, $sh: expr) => {
              write!($fmt,"\t\t{id}[label=\"{name}[{id}]\"][fillcolor={color},shape={shape}];\n" , id=$node.id, name=$node.name, color=$cl, shape=$sh)
	}
}

And a function:

#[inline(always)]
fn write_node(fmt: &mut Write, node: &ComputeNode, color:&str, shape: &str) -> Result<(),Error>{
	write!(fmt,"\t\t{id}[label=\"{name}[{id}]\"][fillcolor={color},shape={shape}];\n"
	, id=node.id, name=node.name, color=color, shape=shape)
}

Now I know the example is a bit extreme, but I'm more interested in what good practices would you suggest. My initial thought is: as long as you can do functions, do functions, in grave danger make a macro.

I don't think we have any hard conventions on this. Here are my rambly thoughts:

  1. When you need to abstract control flow and continuations aren't ergnomic. The major example of this is try!. This should probably be used judiciously because it can make code harder to read for folks that don't know the macro is doing. try! gets a free pass because it is ubiquitous and promoted by std.
  2. Shortcuts over formatting/print functions. Sometimes I write a macro that is like println! but writes to stderr. You can't do this with a function because the implementation is calling println!. (I would say your code might fall in this example.)
  3. When writing impls for many types. If you read std's code, you'll see this a lot, e.g. for numeric/tuple types.
  4. I personally tend to get pretty fast and loose with macros when writing tests, especially if I need to "run all of these tests multiple times with a couple tweaks." Similarly for benchmarks.

I'm less sure on conventions for exporting macros. We don't have too much of that in crate-land, although nom comes to mind.

2 Likes

A good example of for instance what sometimes could be achieved better with macro instead of a function is when u need to borrow a &mut of a trait. Now, you can't do that in a function signiture (it's not sized and the Box won't help to be mutable), but if you do it with macro its ok. For instance this is my (very bad) macro, which runs trough a Vec<Option<T>> and applies a function which takes T -> Result<(),Error> and essentialy stops and immediately returns the error on the first occurrence, or applies it to all options which are not None, on None it just skips (this is something I couldn't manage with normal map, ok_or etc..). My usage is quite regular with having a such vector and having to write its contents to a file, string etc... The problem comes that for writing I need a &mut Write which my functions receives, but I can't send it to another function, can't Box it either. Thus my macro is:
macro_rules! apply_block { ($target:expr, $value:ident, $block: block) => { { for option in $target.iter(){ match *option{ None => (), Some(ref $value) => { match $block { Ok(()) => (), Err(msg) => return Err(msg) } } } } } } }
Now I personally, think its a bid ugly, but want to hear how you guys feel about it? Would such tretchury be really bad, or is it ok?
PS: I love macros, but still am a bit scared of using them (the one I write myself) too much.