Home > Mobile >  Get elements from Vector of tab delimited Strings
Get elements from Vector of tab delimited Strings

Time:08-06

I have a vector of Strings as in the example below, and for every element in that vector, I want to get the second and third items. I don't know if I should be collecting a &str or String, but I haven't gotten to that part because this does not compile.

Everything is "fine" until I add the slicing [1..]

let elements: Vec<&str> = vec!["foo\tbar\tbaz", "ffoo\tbbar\tbbaz"]
    .iter()
    .map(|rec| rec.rsplit('\t').collect::<Vec<_>>()[1..])
    .collect();

It complains because

the size for values of type `[&str]` cannot be known at compilation time
the trait `std::marker::Sized` is not implemented for `[&str]`rustcE0277

CodePudding user response:

As the compiler tells you, the slicing is broken because in Rust a slice returns, well, the slice. Whose size is unknown at compile-time (hence the compiler complaining that it's unsized).

That's why you normally reference the slice e.g.

&thing[1..]

unless it's a context where it doesn't matter. Or you immediately convert the slice to a vector or array.

However here it would not work, because a slice is a "borrowing" structure, it doesn't own anything. And it borrows the Vec being created inside the map, which means you'll get a borrowing error, because the Vec will be destroyed at the end of the callback, and thus the slice would be referencing invalid memory:

error[E0515]: cannot return value referencing temporary value
 --> src/main.rs:5:16
  |
5 |     .map(|rec| &rec.rsplit('\t').collect::<Vec<_>>()[1..])
  |                ^------------------------------------^^^^^
  |                ||
  |                |temporary value created here
  |                returns a value referencing data owned by the current function

The solution is to filter the iterator before collecting the vec, using Iterator::skip:

let elements: Vec<&str> = my_vec
            .iter()
            .map(|rec| rec.rsplit('\t').skip(1).collect::<Vec<_>>())
            .collect();

However this means you now have an Iterator<Item=Vec<&str>>, which doesn't collect to a Vec<&str>.

You could always Iterator::flatten the inner vecs, but in reality they're completely unnecessary: you can just Iterator::flat_map each original string into a stream of strings which automatically get folded into the parent: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=f2c33c1b6a30224202357dc4bd5c1d19

    let my_vec = vec!["foo\tbar\tbaz", "ffoo\tbbar\tbbaz"];
    let elements: Vec<&str> = my_vec
            .iter()
            .flat_map(|rec| rec.rsplit('\t').skip(1))
            .collect();
            
    dbg!(elements);

By the by, the code you're showing doesn't match the description, you say:

for every element in that vector, I want to get the second and third items

but since you're using rsplit what you're getting is the second and first: rsplit will iterate from the end, hence the r for reverse.

  • Related