Is there a reason vec! is a macro rather than two regular functions?
vec! takes two forms:
vec![a, b, c]
vec![v; n]
It would be preferable if these were regular functions rather than macros. One reason is that the type signatures would be clear in the documentation (a, b, c, v need to be of type T, v needs to be Clone, n needs to be usize).
1 already exists as a function: Vec::from([a, b, c]).
2 could be a new function, for example Vec::repeat(v, n).
It can already be written std::iter::repeat(v).take(n).collect() but that's verbose.
In the documentation of vec! simply say that the macro is equivalent to the two functions and those can be used instead.
Saying that vec![v; n] is equivalent to Vec::repeat(v, n) would immediately make it obvious that the expression v gets evaluated once since that follows from regular function call rules.
the vec! macro is extremely useful for teaching the language to people coming from a language like python. they often get caught up trying to use slice literals like they would a heap allocated array in a dynamic language, and telling them to just use vec! if they want something closer to what they're used to is usually the easiest option.
If there's an enduring reason for it, it would be because it allows a different order of evaluation for stuff. The macro can allocate first, then evaluate the parameters one-by-one storing them into the allocation. Something taking an array needs to, at least hypothetically, evaluate the whole argument array before doing the allocation.
But there's absolutely no reason for vec![x; n], since that's purely better done as Vec::repeat(x, n) and has always been possible as a function (indeed, the macro just calls the function).
Well, a less awkward syntax – whether a macro or something else – for creating Strings in Rust is probably one of the most often requested features. I'm sure most people much prefer writing vec![1, 2, 3] over Vec::from([1, 2, 3]) - the ([ combination especially being incredibly awkward to type on many keyboard layouts.
I don't see how that's an argument for a macro vs a function. If you want a shorter notation for string construction, you could just have a shorter function name: string("abc") is both shorter and more understandable than string!("abc").
I don't see how the character combination of ![ is any less awkward to type than ([.
f([...]) or f(&[...]) is standard notation for passing arrays or slices to many other functions. If we wanted a different notation for this, it should apply to all such functions.
Functions have cleaner argument and return type checking and have cleaner docs with standard function signatures.
It's macros that are far more awkward than functions. The syntax isn't clear. The types of arguments aren't clear. What happens with the arguments isn't clear. It all depends on the macro implementation internals. You have to read the docs in prose rather than look at types.
For example I've been confused before (and others too) with how println! automatically takes arguments by reference silently.
Even with vec![x; n] it's not immediately obvious that the argument x is only evaluated once and then cloned rather than evaluated N times, you have to read the docs to be sure.
Until we have some sort of proper guaranteed placement construction, the macro seems like the best option to me. Rust is altogether too happy to copy things at constructions, and relying on LLVM optimisations is brittle to say the least. That (and lack of any form of specialisation, but that is off topic) is one of my largest gripes with Rust currently.
The original reason for the macro was that the const generics feature came relatively late, and without it you can't write a Vec::from that works for a fixed array of any size (you could with slices - but then you'd get references to the values), so a macro was the only way to do it.
All the other reasons for why it is good to have it as a macro are kind of irrelevant - if Rust had const generics from version 1.0.0 it wouldn't have been a macro. And the only valid reason for keeping it a macro is:
There's no guarantee of emplacement. But what it does do is utilize #[rustc_box] to construct the array directly on the heap, which means the (potentially unwinding) happens before constructing the elements (which may have drop glue), making it significantly easier for LLVM to optimize it into direct initialization. Additionally, while every element is created before any are moved to the heap, they remain as separate objects and the slice itself is assembled
That's all implementation details of the current stable compiler, though. We do try to avoid regressing move efficiency, and vec![] is more tested than other less ubiquitous constructions, but it's all best-effort for the medium term at least. The current vec! implementation is also measurably better for compile time than using From<[T; N]>, for essentially the same reasons it's easier to optimize.
Oops, no, I must've copied the wrong version. Fixed; the second was supposed to be using Vec::from to show the difference. Although whether Box::new not getting MIR inlined makes the difference more or less clear, I'm not sure.