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?
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.
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.
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?
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.