Ed McMan
Ed McMan

Reputation: 529

How to succinctly convert an iterator over &str to a collection of String

I am new to Rust, and it seems very awkward to use sequences of functional transformations on strings, because they often return &str.

For example, here is an implementation in which I try to read lines of two words separated by a space, and store them into a container of tuples:

use itertools::Itertools;

fn main() {
    let s = std::io::stdin()
        .lines()
        .map(|l| l.unwrap())
        .map(|l| {
            l.split(" ")
                .collect_tuple()
                .map(|(a, b)| (a.to_string(), b.to_string()))
                .unwrap()
        })
        .collect::<Vec<_>>();

    println!("{:?}", s);
}

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=7f6d370457cc3254195565f69047018c

Because split returns an iterator to &str objects, whose scope is the lambda used for the map, the only way I saw to return them was to manually convert them back to strings. This seems really awkward.

Is there a better way to implement such a program?

Upvotes: 0

Views: 1242

Answers (1)

Sebastian Redl
Sebastian Redl

Reputation: 71919

Rust is explicit about allocation. The Strings returned by the lines() iterator don't persist beyond the iterator chain, so you can't just store references into them. Therefore, logically, there needs to be a to_string() (or to_owned, or String::from) somewhere.

But putting it after the tuple creation is a bit awkward, because it requires you to call the function twice. You can turn the result of the split() into owned objects instead. This should work:

    .map(|l| {
        l.split(" ")
            .map(String::from)
            .collect_tuple()
            .unwrap()
    })
    .collect::<Vec<(_,_)>>();

Note that now you have to be explicit about the tuple type, though.

Upvotes: 4

Related Questions