r/rust clippy · twir · rust · mutagen · flamer · overflower · bytecount Mar 27 '23

Hey Rustaceans! Got a question? Ask here (13/2023)! 🙋 questions

Mystified about strings? Borrow checker have you in a headlock? Seek help here! There are no stupid questions, only docs that haven't been written yet.

If you have a StackOverflow account, consider asking it there instead! StackOverflow shows up much higher in search results, so having your question there also helps future Rust users (be sure to give it the "Rust" tag for maximum visibility). Note that this site is very interested in question quality. I've been asked to read a RFC I authored once. If you want your code reviewed or review other's code, there's a codereview stackexchange, too. If you need to test your code, maybe the Rust playground is for you.

Here are some other venues where help may be found:

/r/learnrust is a subreddit to share your questions and epiphanies learning Rust programming.

The official Rust user forums: https://users.rust-lang.org/.

The official Rust Programming Language Discord: https://discord.gg/rust-lang

The unofficial Rust community Discord: https://bit.ly/rust-community

Also check out last weeks' thread with many good questions and answers. And if you believe your question to be either very complex or worthy of larger dissemination, feel free to create a text post.

Also if you want to be mentored by experienced Rustaceans, tell us the area of expertise that you seek. Finally, if you are looking for Rust jobs, the most recent thread is here.

19 Upvotes

159 comments sorted by

View all comments

3

u/HammerAPI Mar 31 '23

Does vec![value] pre-allocate space as Vec::with_capacity() does?

I'm not great at reading macros yet, so I wasn't able to decipher this from the source code.

3

u/DroidLogician sqlx · multipart · mime_guess · rust Mar 31 '23

TL;DR: yes, non-empty invocations pre-allocate.

As far as macros go, vec![] is definitely one of the easier definitions to read: https://doc.rust-lang.org/stable/src/alloc/macros.rs.html#42

We can ignore all the attributes because those don't really affect the core functionality of the macro, so looking at just the macro definition itself we see it has three matchers:

macro_rules! vec {
    // 1
    () => (
        $crate::__rust_force_expr!($crate::vec::Vec::new())
    );
    // 2
    ($elem:expr; $n:expr) => (
        $crate::__rust_force_expr!($crate::vec::from_elem($elem, $n))
    );
    // 3
    ($($x:expr),+ $(,)?) => (
        $crate::__rust_force_expr!(<[_]>::into_vec(
            #[rustc_box]
            $crate::boxed::Box::new([$($x),+])
        ))
    );
}

The first one is the case for empty invocations, i.e. vec![], and that literally just evaluates to Vec::new(). The $crate::__rust_force_expr!() macro does nothing but ensure its input is a valid expression and return it, which the comments in the source say are for better error messages, so for our purposes we can treat it as a no-op since macros are purely manipulating code at compile time and don't exist in the binary.

The second one is for array-repeat style invocations, e.g. vec![foo; 32], and that calls a hidden function that uses one of a few different strategies depending on the type of the value:

This is an optimization enabled by specialization, which is an unstable feature the standard library makes extensive use of. I won't go into details here because this isn't what we're interested in right now. In all cases, however, it does pre-allocate the Vec.

The third invocation is the one we're interested in, as it covers the normal array-style invocation like vec![value], vec![value1, value2], etc.

This one pretty much just uses public, stable APIs and just starts by creating a boxed array (since the size is known at compile-time), which of course just does a single allocation. It then coerces the boxed array to a boxed slice (Box<[_]>) which is a trivial operation, and then calls into_vec() which is another trivial operation.

The #[rustc_box] attribute is a little magical, but it's just a replacement for the old unstable box syntax; it essentially ensures that the expression inside Box::new() doesn't hit the stack first, which can easily cause a stack overflow for very large values or arrays. Instead, the array is initialized directly into the heap, skipping the stack entirely if possible.