Type annotating a Vec with square brackets

I'm reading through "The Rust Programming Language" and on chapter (section?) 10 I found the following code (listing 10-3):

fn largest(list: &[i32]) -> i32 {

The list argument here is supposed to be a Vec, in fact the function is called on the following variable:

let number_list = vec![34, 50, 25, 100, 65];

What confuses me is using [i32] to annotate a vector, since that is how arrays are annotated. On my code I used Vec<i32>, is it also possible to use [i32], if so, how does one (or the compiler) differentiate between vectors and arrays?

&[i32] is a slice, not a Vector; we can go from Vector -> &[i32] via ".as_slice()"

1 Like

[i32] is a “slice” type.

Vec<i32> implements Deref<Target = [i32]>, which means it acts like a pointer to a slice. The compiler will automatically convert &Vec<i32> to &[i32] in a process called deref coercion.


And one more thing, vec![] is macro, which creates Vec<...>. I believe it expands to Vec::new() followed by a number of Vec::push() calls.

Actually the expansion is more-or-less a single <[_]>::into_vec(Box::new([a, b, c, d, etc])) call. That is: for the case of calling vec![a, b, c, d, etc] — i.e. not for vec![expr; N], that one has a different implementation.


Thanks for the correction. Surprised I misunderstood it all this time. What is the advantage of allocating a fixed size array on the heap followed by into_vec over with_capacity + push ?

Easier to optimize probably -- arrays and Vecs both have the same layout (same as a slice), so that will likely just emit a memcpy. Well, let's test this...

On Debug, LLVM IR and ASM both show an eventual call to copy_nonoverlapping to memcpy.

  call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 1 %1, i8* align 1 %2, i64 %0, i1 false), !dbg !282
	callq	memcpy@PLT

On release this gets optimized away to initialize in place from static memory + a register.

1 Like

That means that the &Vec<i32> type implements the deref coercion that automatically takes care of the conversion when the compiler detects that I'm passing an argument of type &Vec<i32> into a function that wants a &[i32], right?

Yes. By the way, it’s the same with &String and &str. The type String is like Vec<u8>, and &str is like &[u8] in this regard. E.g.

fn length(x: &str) -> usize {

fn main() {
    let s: String = "Hello, World!".into();
    println!("{}", length(&s));

(in the playground)

1 Like

I see, but I that means that string slices have their own type (str)? It seems from the documentation that the slice type is more general, for example it can be used both for vector and arrays:

// slicing a Vec
let vec = vec![1, 2, 3];
let int_slice = &vec[..];

let mut x = [1, 2, 3];
let x = &mut x[..]; // Take a full slice of `x`.

Are strings a special case that uses its own type of slice &str?

Yes, string slices have their own type. You can cheaply convert from &str to &[u8] though. (Using str::as_bytes.) The other way is not as easy, yet still possible without doing new allocation, but it needs to inspect the whole slice and is fallible (using str::from_utf8). The reason: a &str is basically the same as a &[u8] internally, however a &str is guaranteed to contain only valid UTF-8 data.

There's also no "array"-like type for strings. Yet &str still unifies a bunch of types like &String, &Box<str>, &Rc<str>, &Cow<str>, etc, and – as explained above – it can also come from a &[u8], so as long as you're checking the data for validity, you can create a &str that borrows from a Vec<u8> or even a [u8; N] array. Furthermore, &str can also be a pointer into static memory for string literals. I.e. the string literal's data will be part of the executable itself and loaded into immutable memory alongside the program's instructions at the start of your program execution; then a &str slice can point to that static memory. This is the reason why string literals have type &'static str.


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.