Idea: syntactic sugar for if(let)/for/extend/append in vec! macro

In some logic that constructs a Vec<T>, certain conditions may have to satisfy before some items be added at a certain position, or it may be necessary to use a for, extend an IntoIterator<T> or append another Vec<T> at a certain position. Currently once any of these logic is in place, vec! must be split. It might be more ergonomic if vec! can integrate these logic with some kind of syntactic sugar. The effect would probably be like this:

let mut v = Vec::new();
v.push(1);
if true {
    v.push(2);
}
for n in 3..5 {
    v.push(n);
}
v.append(&mut vec![6, 7, 8]);
v.extend([9, 10]);
if let Some(n) = Some(11) {
    v.push(n);
}
v

is equivalent to

vec_ext![
    1,
    @if true {
        2
    },
    @for i in 3..5 {
        i
    },
    @append(&mut vec![6, 7, 8]),
    @extend([9, 10]),
    @iflet Some(n) = Some(11) {
        n
    },
]

The syntactic details need to be discussed further. Thanks to Rust's macro system, we can use ordinary macro_rules or proc macro to try to implement it, unlike other languages where literal is directly at the language syntax level. Even if the discussion results in it not being suitable for inclusion in std (which seems likely), it can be implemented in a separate crate for those who need it. I will try to write an implementation later.
Originally posted in internals, but since it is unlikely to make it to std, perhaps it would be more appropriate to post it here.

There isn't need for anything like this. Everything you wrote already has a much nicer, uniform, concise equivalent in the iterator library: Playground

fn iterator() -> Vec<u64> {
    [1].into_iter()
        .chain(true.then_some(2))
        .chain(3..5)
        .chain(vec![6, 7, 8].drain(..))
        .chain([9, 10])
        .chain(Some(11))
        .collect()
}
11 Likes

Personally I'd love something like this. The problem with the imperative and chaining approaches is that they both add lots of noise.

As prior art, Python natively supports an equivalent to your @extend:

list1 = [3, 4, 5]
list2 = [1, 2, *list1] # [1, 2, 3, 4, 5]

This can be turned into an equivalent to your @for by unpacking a generator expression:

list2 = [2, 3, *(x + 1 for x in list1)] # [2, 3, 4, 5, 6]

@if can be achieved too with the following pattern, but it's much uglier and doesn't play nice with type checkers:

cond = True
list3 = [1, 2, *([3] if cond else [])] # [1, 2, 3]

However, I sometimes use that pattern anyway, particularly with long multiline lists, because it just feels nicer to have everything in a list literal rather than breaking it up.

2 Likes

There is Python-like comprehensions: list_comprehension_macro — Rust implementation // Lib.rs

1 Like

My current implementation: https://github.com/Berylsoft/foundations/blob/875f00b/vec_ext.rs
The syntax is similar to the example above:

vec_ext![
    1,
    @if (true) {
        2
    },
    @for (i in 3..5) {
        i
    },
    @append(&mut vec![6, 7, 8]),
    @extend([9, 10]),
    @if let (Some(n) = Some(11)) {
        n
    },
]

It was actually written the same day the thread posted and basically meets my needs, and may be expanded later. Sorry for forgetting to give feedback earlier.

The closest equivalent is probably Scala’s for conprehensions where you can write something like

val xs = [1, 4, 6]
val y = Some(5)
val result = for 
    x <- xs if x % 2 == 0
    y <- y_opt
yield x*x + y

and it’s mechanically transformed to the equivalent map/flatmap/filter chain and works with any combination of type that have the appropriate methods.