What is `vec!()`? Is there a diff vs. `vec![]`?

I came across vec!() in code, being used to create an empty Vector. I'd never seen that syntax before. It appears to be equivalent to vec![], but I want to understand what exactly the parens syntax is doing: why it is valid syntax, how it works, what difference (if any) it has to the brackets syntax, when I would prefer one form over the other, etc.

2 Likes

There is no difference. You can call macros in expression position with any of (), [] or {} as delimiters.

As for vec![], square brackets are more idiomatic, because it's an array-like collection, and [] is used for arrays in Rust (and many other languages). It's also the syntax for indexing. Generally, for function-like macros, you would use round parentheses () instead, because those look like a function call. But you could as well write println!{} or write![] if you wanted to, although it would not be considered idiomatic.

17 Likes

There is one minor difference between () / [] and {}, as explained in the tt-call docs:

The Rust grammar is very particular about punctuation after parenthesized and square bracketed macro invocations. In expression or type position they must not be followed by a semicolon. In item or statement position they are required to be followed by a semicolon. [...] There is no such punctuation requirement after curly brace invocations.

But you probably don't need to worry about this, unless you write a lot of macros yourself.

5 Likes

Interesting, that is a bit surprising. I checked some sample code with clippy, but apparently nothing warns you to use the right parentheses.

fn main() {
    let a = vec![1, 2, 3];
    let b = vec!(1, 2, 3);
    let c = vec!{1, 2, 3};
    println!("{:?}", a);
    println!["{:?}", b];
    println!{"{:?}", c};
}

(Playground)

I think that Rust would benefit from an annotation on macro declarations that specifies the correct type of parentheses to use. The compiler can then generate a warning or an error if the wrong type is used. This way, people would not accidentially write non-idiomatic code, potentially confusing others about the meaning of a macro invocation.

Interestingly, rustfmt already is aware of wrong parentheses. When executing it in the playground, it corrects the parentheses of the vec! invocations to [].

In your example, vec! is in expression position, so there is no "wrong" or "right" parenthesis to use.

No, it definitely can't generate an error. That would be a breaking change. (Especially in e.g. generated code, where, for simplicity of implementation, only a single type of parenthesis is used.)

In the case of vec! it is idiomatic to use [], isn't it? Then, it would make sense to discourage using parentheses different to [] for the vec! macro.

It is true that generating an error would be a breaking change. How much breaking changes are wanted (between editions of course) seems to be a different topic though, that I didn't want to open up now.

Good point about generated code. If you want to use different parentheses there than those annotated in the macro definition, you could add an #[allow(discouraged_macro_parentheses)] or so somewhere.

Let me clarify a little what I mean:

#[parentheses="()"]
macro_rules! abc {...}

macro_rules! def {...}

fn main() {
    abc!(...) // Ok
    abc![...] // Denied via lint, warning or error
    abc!{...} // Denied via lint, warning or error
    def!(...) // Ok
    def![...] // Ok
    def!{...} // Ok
}

The goal is to encourage Rust code to look the same everywhere where possible, to make it easier to read.

Something like this, where it is more about idioms than the potential for incorrect code, makes more sense as a clippy lint rather than a compiler warning/error. There is already a clippy lint for nonstandard braces on common macros, but it is set to allow by default, because it seems to be a fairly recent addition.

2 Likes

What I noticed is that macros seem to act a bit differently from function calls also when it comes to trailing commas in argument lists. At least rustfmt doesn't seem to normalize these.

Yeah, since macros can have arbitrary syntax, it's impossible for rustfmt to know for sure if adding/removing a trailing comma is safe. So it doesn't.

See format macros · Issue #8 · rust-lang/rustfmt · GitHub.

3 Likes

rustfmt will replace vec!{ /*...*/ } or vec!( /*...*/ ) with vec![ /*...*/ ]

1 Like

If you really try, rustfmt and vec! can even change the behaviour of the program: https://twitter.com/m_ou_se/status/1440771870177980421?t=rtJUas2BQ5MSbhgCuGVZ4A&s=19

7 Likes

ow

And that's why I…:

  • don't trust any automatic tool that changes my code
  • don't buy the "but the type system will catch it" argument for proposals describing syntactically dangerous features. No, it just won't.
2 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.