How do I convert a Vec<String> to Vec<&str>?

20,748

Solution 1

There are quite a few ways to do it, some have disadvantages, others simply are more readable to some people.

This dereferences s (which is of type &String) to a String "right hand side reference", which is then dereferenced through the Deref trait to a str "right hand side reference" and then turned back into a &str. This is something that is very commonly seen in the compiler, and I therefor consider it idiomatic.

let v2: Vec<&str> = v.iter().map(|s| &**s).collect();

Here the deref function of the Deref trait is passed to the map function. It's pretty neat but requires useing the trait or giving the full path.

let v3: Vec<&str> = v.iter().map(std::ops::Deref::deref).collect();

This uses coercion syntax.

let v4: Vec<&str> = v.iter().map(|s| s as &str).collect();

This takes a RangeFull slice of the String (just a slice into the entire String) and takes a reference to it. It's ugly in my opinion.

let v5: Vec<&str> = v.iter().map(|s| &s[..]).collect();

This is uses coercions to convert a &String into a &str. Can also be replaced by a s: &str expression in the future.

let v6: Vec<&str> = v.iter().map(|s| { let s: &str = s; s }).collect();

The following (thanks @huon-dbaupp) uses the AsRef trait, which solely exists to map from owned types to their respective borrowed type. There's two ways to use it, and again, prettiness of either version is entirely subjective.

let v7: Vec<&str> = v.iter().map(|s| s.as_ref()).collect();

and

let v8: Vec<&str> = v.iter().map(AsRef::as_ref).collect();

My bottom line is use the v8 solution since it most explicitly expresses what you want.

Solution 2

The other answers simply work. I just want to point out that if you are trying to convert the Vec<String> into a Vec<&str> only to pass it to a function taking Vec<&str> as argument, consider revising the function signature as:

fn my_func<T: AsRef<str>>(list: &[T]) { ... }

instead of:

fn my_func(list: &Vec<&str>) { ... }

As pointed out by this question: Function taking both owned and non-owned string collections. In this way both vectors simply work without the need of conversions.

Solution 3

another_items.iter().map(|item| item.deref()).collect::<Vec<&str>>()

To use deref() you must add using use std::ops::Deref

Solution 4

All of the answers idiomatically use iterators and collecting instead of a loop, but do not explain why this is better.

In your loop, you first create an empty vector and then push into it. Rust makes no guarantees about the strategy it uses for growing factors, but I believe the current strategy is that whenever the capacity is exceeded, the vector capacity is doubled. If the original vector had a length of 20, that would be one allocation, and 5 reallocations.

Iterating from a vector produces an iterator that has a "size hint". In this case, the iterator implements ExactSizeIterator so it knows exactly how many elements it will return. map retains this and collect takes advantage of this by allocating enough space in one go for an ExactSizeIterator.

You can also manually do this with:

let mut items = Vec::<&str>::with_capacity(another_items.len());
for item in &another_items {
    items.push(item);
}

Heap allocations and reallocations are probably the most expensive part of this entire thing by far; far more expensive than taking references or writing or pushing to a vector when no new heap allocation is involved. It wouldn't surprise me if pushing a thousand elements onto a vector allocated for that length in one go were faster than pushing 5 elements that required 2 reallocations and one allocation in the process.

Another unsung advantage is that using the methods with collect do not store in a mutable variable which one should not use if it's unneeded.

Solution 5

This one uses collect:

let strs: Vec<&str> = another_items.iter().map(|s| s as &str).collect();
Share:
20,748
Kalita Alexey
Author by

Kalita Alexey

Updated on November 19, 2020

Comments

  • Kalita Alexey
    Kalita Alexey over 3 years

    I can convert Vec<String> to Vec<&str> this way:

    let mut items = Vec::<&str>::new();
    for item in &another_items {
        items.push(item);
    }
    

    Are there better alternatives?

  • huon
    huon over 8 years
    Explicitly calling deref (especially as a free function) reads quite strangely IMO, I findv2 or v5 nicer personally. (One could also use as_ref, v.iter().map(|s| s.as_ref())... which is the typical method for generic conversions of this form, e.g. many functions in std take P: AsRef<Path>.)
  • oli_obk
    oli_obk over 8 years
    thanks, you are totally right. AsRef is much more appropriate here. I updated the post
  • JeanMertz
    JeanMertz almost 5 years
    In the v8 example, you can also call String::as_str, which makes it even more clear what the intent is of your code.
  • Shepmaster
    Shepmaster over 3 years
    This doesn't appear to answer the question: "How do I convert a Vec<String> to Vec<&str>?"
  • Zorf
    Zorf over 3 years
    @Shepmaster Correct, it was was already answered in many ways. I'm simply pointing out that all of them neglected to answer why their answers are idiomatic Rust opposed to ops suggestion. Correctly saying that they are better, without explaining why it is better seems like a poor learning exercise in to me.
  • Piotr Zakrzewski
    Piotr Zakrzewski over 2 years
    The s as &str makes most sense to me, the idomatic way is hard to understand and remember IMO.
  • rsalmei
    rsalmei about 2 years
    So, there isn't a way without allocating?? collect allocates a new vector.