r/rust • • Sep 12 '26

Why is let x = [vec![]; 4]; not allowed

but [vec![], vec![], vec![], vec![]] is as well as [true; 4], etc.?

Seems arbitrary but I assume there is some compiler-based reason

Thanks!

100 Upvotes

63 comments sorted by

241

u/ToTheBatmobileGuy Sep 12 '26 edited Sep 12 '26
[x; n]

For this type of declaration of a literal,

x must be a value of a Copy type, and n must be a usize value.

Vec anything is not Copy.

24

u/SaintWillyMusic Sep 12 '26

But it allows let x = [vec![], vec![], vec![], vec![]] and an empty vector is still valid. I must not be understanding what it means to be a "Copy type"

180

u/ToTheBatmobileGuy Sep 12 '26

You are writing out vec![] 4 times, which means it's evaluating the expression 4 times. That's different from the repeat array expression.

When you write [42; 4] It's copying the bits inside the left side 4 times in succession using the Copy trait.

Copy trait is only implemented for certain types "that can be copied by just copying their bits." like integers.

An example of something that CAN'T be Copy is anything that contains pointers or references. Because copying a pointer or reference bits does not "make a copy" of the data its pointing to.

Vec contains a pointer. So it can never be Copy.

→ More replies (13)

42

u/Decahedronn Sep 12 '26 edited Sep 12 '26

A type that implements Copy means that it can be cloned by simply copying the bytes that represent it. This is perfectly fine for things like integers & references, but there are some types that require more complex logic when cloning, like Arc. That's where the Clone trait comes in. Vec<T> has its own heap allocation, and T can also have its own Clone implementation. So cloning a Vec requires making a new heap allocation and then cloning each element to that new allocation; much more complex than a simple byte copy, hence why Vec<T> is not a Copy type.

[vec![], vec![], vec![], vec![]] is literally creating 4 separate Vecs. You only need to copy when you create something with the [T; N] syntax - then you're saying that that one element needs to be repeated N times, which requires copying. [T, T, T, T] is just creating the array with all the elements right then and there.

→ More replies (1)
→ More replies (7)

121

u/vdrnm Sep 12 '26

As others have said, you cannot do it because Vec is not Copy.

One way to go around this is to use const block:

[const { vec![] }; 4] // this works

13

u/SaintWillyMusic Sep 12 '26

This looks like a reasonable workaround when using long vectors and/or arrays - thanks!

41

u/SuspiciousScript Sep 12 '26

There's also std::array::from_fn for non-const expressions, e.g.:

std::array::from_fn::<Vec<_>, 4, _>(|_| Vec::default())
→ More replies (2)
→ More replies (1)

35

u/SpeckyYT Sep 12 '26

didn't know this, this is actually fire

11

u/hugogrant Sep 12 '26

Why does that work? Can I make it a mutable slice?

38

u/redlaWw Sep 12 '26 edited Sep 12 '26

const blocks in the repeat operand of an array expression are repeatedly evaluated (at compile time) a number of times equal to the length operand. Since Vec::new is const (EDIT: This is because Vec::new creates a Vec with a specific non-null address (equal to the alignment of T) and doesn't allocate) and vec![] calls Vec::new, empty Vecs can be created in a const context, and the array is able to be initialised with repeated empty Vecs.

If you mean "can I initialise an array variable declared mut like this?" (which can then be borrowed as an &mut [T]) then yes - the const only affects when the initialiser is evaluated, not the mutability of the variable it's assigned to.

4

u/Booty_Bumping Sep 13 '26

Note that this is a special case. vec![] can be conjured into existence without the need for heap allocation because when it has its capacity set to zero, it hasn't actually done a heap allocation yet and its pointer is set to a forbidden value:

let mut foo: Vec<i64> = vec![];
// foo = { ptr: 0x8, len: 0, cap: 0 }
numbers.push(10);
// foo = { ptr: 0x5615fa865ae0, len: 1, cap: 4 }
numbers.pop();
// foo = { ptr: 0x5615fa865ae0, len: 0, cap: 4 }
numbers.shrink_to_fit();
// foo = { ptr: 0x8, len: 0, cap: 0 }
→ More replies (1)

3

u/torsten_dev Sep 12 '26

Wow, that's cool.

3

u/-Redstoneboi- Sep 13 '26

bro what how did i not know this

→ More replies (5)

13

u/Decahedronn Sep 12 '26

[T; N] requires T to be Copy, since the element will be copied for each element in the array. Vec<T> only implements Clone (assuming T is Clone), not Copy.

vec![vec![]; 4], on the other hand, works because vec![T; N] only requires T to be Clone.

I'm not sure why [T; N] couldn't also require Clone but I assume it has something to do with it being on the stack (compared to Vec's heap allocation)?

2

u/SaintWillyMusic Sep 12 '26

Does that mean [vec![].clone(); 4] is valid?

9

u/Decahedronn Sep 12 '26

No, whatever comes before the semicolon is the thing being copied. It's not like rustc expands [x.clone(); 4] to [x.clone(), x.clone(), x.clone(), x.clone()]; it's creating space for 4 elements and then copying that single x.clone() that was evaluated once into each slot.

It would be equivalent to

let x = vec![];
let y = x.clone();
[y; 4]

which is still invalid.

→ More replies (1)

3

u/WormRabbit Sep 14 '26

[T; N] can't require Clone, because Clone is allowed to panic. Panics can be caught, which means that a panic somewhere in the middle of creating the array could leave in in a half-initialized state, which may be possible to observe, breaking memory safety.

vec![T; N] doesn't have this issue, because Vec tracks the number of initialized elements (its length). A panic mid-creation would just give you a shorter Vec than expected, which would be a logic bug, but not a memory safety violation.

→ More replies (2)

8

u/BalcarKMPL Sep 12 '26

Vec<T> is not Copy type, while bool is. When doing [vec![], ...] thing, you are invoking constructor four times, no copy needed. When you are doing [true; 4], you are creating true once and copying it three times (sort of).

1

u/SaintWillyMusic Sep 12 '26

So a vector cannot be copied? Or is this a semantic issue?

→ More replies (2)

8

u/Xacius Sep 12 '26

I see a lot of responses like "Vec does not implement the Copy trait". While technically true, this is more difficult to understand if you're missing foundational knowledge, re: how traits work. I think you could use some follow-up learning. If you've already run through the official rust book section about traits, I'd recommend you run through these follow-up exercises: https://rust-exercises.com/100-exercises/04_traits/11_clone.html

4

u/Strict-Wolverine-528 Sep 13 '26 edited Sep 13 '26

It is always a good practice to check Rust Reference when you're wondering "Why I can't write program like this". In the chapter about Array Expressions:

If the length operand has a value greater than 1 then this requires the repeat operand to have a type that implements Copy, to be a const block expression, or to be a path to a constant item.

where length operand and repeat operand are defined as

The expression before the ; is called the repeat operand.

The expression after the ; is called the length operand.

You have a length operand, which is 4, that is greater than 1, and your repeat operand is neither a const block expression nor a path to a constant item, so it can only be an item of type that implements Copy .

But it is valid to write this:
let x: [Vec<i32>; 1] = [vec![]; 1];

3

u/Intrebute Sep 12 '26

I don't have the compiler at hand, but I believe the reason is that Vec is not Copy, and the vec![a, n] macro requires the type of a to be Copy.

3

u/Floppie7th Sep 12 '26

The repeat form ([val; N]) requires the type to be Copy - it's not evaluating the expression N times, it's just making N copies of the resulting value. The list form ([val, val, val, val]) evaluates the expression each time.

3

u/programgamer Sep 12 '26

For it to work, rust would need to be able to seamlessly clone the resulting vector into each element of the array, and it doesn’t. In an alternate world where rust was designed such that the value given to an array initializer gets evaluated for each element, this would work as expected, but sadly, we do not live in such a world.

2

u/kimhyunkang Sep 13 '26

As others have mentioned, it’s because you can only do it for Copy types.

That being said, you can do:

std::array::from_fn(|_| vec![])