Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I convert a list of Option<T> to a list of T when T cannot be copied? [duplicate]

How do I take a Vec<Option<T>>, where T cannot be copied, and unwrap all the Some values?

I run into an error in the map step. I'm happy to move ownership of the original list and "throw away" the Nones.

#[derive(Debug)]
struct Uncopyable {
    val: u64,
}

fn main() {
    let num_opts: Vec<Option<Uncopyable>> = vec![
        Some(Uncopyable { val: 1 }),
        Some(Uncopyable { val: 2 }),
        None,
        Some(Uncopyable { val: 4 }),
    ];

    let nums: Vec<Uncopyable> = num_opts
        .iter()
        .filter(|x| x.is_some())
        .map(|&x| x.unwrap())
        .collect();
    println!("nums: {:?}", nums);
}

Playground

Which gives the error

error[E0507]: cannot move out of borrowed content
  --> src/main.rs:17:15
   |
17 |         .map(|&x| x.unwrap())
   |               ^-
   |               ||
   |               |hint: to prevent move, use `ref x` or `ref mut x`
   |               cannot move out of borrowed content
like image 762
xrl Avatar asked Jun 02 '15 06:06

xrl


2 Answers

In Rust, when you need a value, you generally want to move the elements or clone them.

Since move is more general, here it is, only two changes are necessary:

let nums: Vec<Uncopyable> = num_opts
    .into_iter()
//  ^~~~~~~~~~~~-------------- Consume vector, and iterate by value
    .filter(|x| x.is_some())
    .map(|x| x.unwrap())
//       ^~~------------------ Take by value
    .collect();

As llogiq points out, filter_map is specialized to filter out None already:

let nums: Vec<Uncopyable> = num_opts
    .into_iter()
//  ^~~~~~~~~~~~-------- Consume vector, and iterate by value
    .filter_map(|x| x)
//              ^~~----- Take by value
    .collect();

And then it works (consuming num_opts).

As pointed out by @nirvana-msu, in Rust 1.33 std::convert::identity was added which can be used instead of |x| x. From the documentation:

let filtered = iter.filter_map(identity).collect::<Vec<_>>();
like image 51
Matthieu M. Avatar answered Nov 12 '22 15:11

Matthieu M.


You don't need to copy the Uncopyable at all, if you are OK with using a Vec of references into the original Vec:

let nums: Vec<&Uncopyable> = num_opts.iter().filter_map(|x| x.as_ref()).collect();
//            ^ notice the & before Uncopyable?

This may not do the trick for you if you have to work with an API that requires &[Uncopyable]. In that case, use Matthieu M.'s solution which can be reduced to:

let nums: Vec<Uncopyable> = num_opts.into_iter().filter_map(|x| x).collect();
like image 43
llogiq Avatar answered Nov 12 '22 14:11

llogiq