turbulencetoo
turbulencetoo

Reputation: 3701

Create Vec<String> from literal

I want to do

fn main() {
    let options: Vec<String> = vec!["a", "b", "c"].map(|s| s.to_owned()).collect();
}

because this seems like the easiest way to get a vector of owned Strings, but I get hit with this error:

error: no method named `map` found for type `std::vec::Vec<&str>` in the current scope
...
note: the method `map` exists but the following trait bounds were not satisfied:
`std::vec::Vec<&str> : std::iter::Iterator`, `[&str] : std::iter::Iterator`

I don't see where the need for the [&str] : std::iter::Iterator bound comes from. if you ignore the part with split_whitespace I'm basically doing what answers on this question recommend.

How should I be generating this vector?

Upvotes: 13

Views: 9978

Answers (2)

Boiethios
Boiethios

Reputation: 42849

If I had to create vectors of Strings repeatedly, I would use a macro:

macro_rules! vec_of_strings {
    // match a list of expressions separated by comma:
    ($($str:expr),*) => ({
        // create a Vec with this list of expressions,
        // calling String::from on each:
        vec![$(String::from($str),)*] as Vec<String>
    });
}

fn main() {
    let v1 = vec_of_strings![];
    let v2 = vec_of_strings!["hello", "world", "!"];

    println!("{:?}", v1);
    println!("{:?}", v2);
}

Output:

[]
["hello", "world", "!"]

Upvotes: 2

Shepmaster
Shepmaster

Reputation: 431809

if you ignore the part with split_whitespace

Yes, except you cannot ignore this part. The docs for split_whitespace state (emphasis mine):

The iterator returned

split_whitespace returns an iterator over the pieces of the string that were separated by whitespace, and map is a method on Iterator.

A Vec is not an iterator. You can see that Vec does not implement it's own map method:

no method named map found for type std::vec::Vec<&str>

And the compiler tries to suggest what you might have meant, but weren't quite achieving:

note: the method map exists but the following trait bounds were not satisfied:

You can get an iterator from a Vec by calling Vec::iter or into_iter:

fn main() {
    let options: Vec<String> = vec!["a", "b", "c"].into_iter().map(|s| s.to_owned()).collect();
}

However, there's no need to allocate two vectors here, an array and a vector is more efficient:

let options: Vec<_> = ["a", "b", "c"].iter().map(|s| s.to_string()).collect();

An iterator over a slice returns references (&T) to the elements in the slice. Since each element is already a &str, the type of s is a &&str. Calling to_owned on a reference to a reference simply clones the reference. You could also have said .map(|&s| s.to_owned()), which dereferences the value once, producing a &str. Calling to_owned on a &str allocates a String.

Upvotes: 15

Related Questions