Why does a &[String] slice work on a Vector<String>?

It's common to write functions to take slices, for the maximum flexibility.
For example, a function to analyze a string is usually written to take &str ( a slice of a string), and a function to analyze an integer array might take &[i32], and so on.

Recently I saw an example whereby a method takes &[String] but what is passed to it is a reference to a Vector:

let args: Vec<String> = env::args().collect();
parse_args(&args);
...
fn parse_args(args: &[String])  {...}

Maybe it's my other programming languages polluting my brain, but why is Vector of Strings compatible with &[String]? .. I can understand &[String] being a slice for an array of Strings, but a vector is not an array...

Michael

This is because Vec<T> implements the Deref trait with Target = [T] , which means that the compiler will implicitly convert &Vec<T> to &[T] when called for ("deref coercion").

The intuition is that while a Vec<T> is not the same thing as a [T] (unsized slice of T), it's a kind of "smart pointer" to a [T] , so if all you need is a &[T] then a &Vec<T> will work just fine.

2 Likes

&Vec<T> -> &[T]

&Vec<String> -> &[String]

&String -> &str

&[String] !~ &str

&Vec<String> !~ &String

2 Likes

Awesome, thank-you. Not only did you answer the question, you've sent me off on a new learning adventure regarding "Deref" and "deref coercion" ! (so many things to learn/understand about Rust!)

1 Like

Well, Deref really is more like &Vec<T> -> &[T], &String -> &str, etc.

1 Like

Deref is cool stuff for sure. However, keep in mind &Vec and &String (and array for that matter) have a special relationship with slice. Special in that *my_vec_ref is consistent because it is a ref to the memory block.

Another useful, and only somewhat “debated” use is to implement Deref for a newtype, single purpose, wrapper.

You’re right. I corrected it. Thank you!

I'm not sure why you expect that only arrays could be sliced. The point of a slice is that it's a view (in particular a pointer-length pair) into a contiguous block of memory. It doesn't matter where that memory lives.

The differences between an array and a vector are:

  • arrays store their values inline, while vectors allocate a separate buffer;
  • arrays go on the stack by default (unless you Box them or something), while vectors unconditionally allocate their buffer on the heap;
  • the fixed buffer of an array has a known compile-time size; the out-of-line buffer of a vector can grow and shrink dynamically.

Now, none of this matters when it comes to converting to a slice. The only difference is that the pointer of the resulting slice points into the in-line, probably stack-allocated, buffer of the array, while the vector hands out a pointer inside of its out-of-line, heap-allocated buffer (observe this playground). But in both cases, you'll get a slice that's essentially equivalent to a { first: &T, len: usize } pair.

2 Likes

Thanks, I wasn't thinking Vectors couldn't be sliced, it was that it seemed strange to represent such a slice as &[] which "looks like" a slice to an array type, not a Vector type. I would have originally expected a vector slice to look something like &Vector[..] or somehow use the text "Vector".

This is one of the aspects of learning Rust that still surprise/confuse me. At times it's extremely pedantic about matching up types exactly and in other cases it seems like the syntax allows for type "dissonance", at least to me..

The […] syntax is simply indexing, it's common in many other languages, too. I.e. [] in postfix position means "index", not "array". It's not the same as [] in prefix position, where it indeed means an array constructor. Incidentally, you can use indexing on HashMap and BTreeMap, too, for instance, even though they are not related to an array data structure.

Similarly, you can use binary + for addition on integers, floating-point numbers and even multi-dimensional arrays/tensors, even though these are distinct types and the mechanics of performing arithmetic are completely different and unique to each. Yet, you probably wouldn't insist that there be a separate +f32 or +i64 operator for each different type that supports addition.

What’s the use case for doing so? Conversion to array??

Well, the map lookup.

let mut m = HashMap::new();
m.insert("foo", "bar");
assert_eq!(m["foo"], "bar");
2 Likes

This is wrong, it's actually &String -> &str

5 Likes

Isn't this true for every implementation of Deref, not just for Vec and String?

Oh, for some reason I thought that indexing happened without a separate indexing call.

I blame it on COVID and fixed it. (It’s not COVID of course, it’s typing on my iPhone :)))

1 Like

That’s the intent.