Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to take ownership of a value in flat_map

Tags:

iterator

rust

With the following simplified/abstracted version of my problem:

fn main() {
    let foo_selectors = vec![0, 1];

    let foos: Vec<_> = foo_selectors
        .into_iter()
        .flat_map(|i| get_foo(i).into_iter())
        .collect();

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

fn get_foo(i: u8) -> [u8; 3] {
    if i % 2 == 0 {
        [1, 2, 3]
    } else {
        [4, 5, 6]
    }
}

Playground Link

I get the following error message:

error[E0597]: borrowed value does not live long enough
  --> src/main.rs:6:44
   |
6  |         .flat_map(|i| get_foo(i).into_iter())
   |                       ----------           ^ temporary value dropped here while still borrowed
   |                       |
   |                       temporary value created here
...
10 | }
   | - temporary value needs to live until here

The error message complains about borrowing, but I've used into_iter everywhere, which as I understand it, takes ownership of the iterated values. I want to take ownership of the values returned from get_foo and insert their elements into foos. How can I do that?

like image 477
Ryan1729 Avatar asked Aug 27 '17 12:08

Ryan1729


1 Answers

How to take ownership of a value in flat_map

There's nothing special about flat_map here. When calling get_foo, ownership of the return value is transferred to the caller, just like anywhere else in Rust.

I've used into_iter everywhere, which as I understand it, takes ownership of the iterated values.

Usually, yes, but not for an array. See the linked questions below for why. This is the source of the problem. The flat_map closure owns the result of get_foo, and then you take a reference to it. The reference cannot live beyond the closure, but that's what you are asking it to do.

As a workaround, you can return a Vec from your function:

fn get_foo(i: u8) -> Vec<u8> {
    if i % 2 == 0 {
        vec![1, 2, 3]
    } else {
        vec![4, 5, 6]
    }
}

You could also convert the returned array into a Vec inside the flat_map call.

If you felt like the situation warranted it, you could implement your own iterator:

struct CloneArrayIter<T> {
    arr: [T; 3],
    idx: usize,
}

impl<T> CloneArrayIter<T> {
    fn new(arr: [T; 3]) -> Self {
        Self { arr, idx: 0 }
    }
}

impl<T> Iterator for CloneArrayIter<T>
where
    T: Clone,
{
    type Item = T;

    fn next(&mut self) -> Option<Self::Item> {
        if self.idx < 3 {
            let value = self.arr[self.idx].clone();
            self.idx += 1;
            Some(value)
        } else {
            None
        }
    }
}

And use it as .flat_map(|i| CloneArrayIter::new(get_foo(i)))

I'd probably just use an ArrayVec from arrayvec though:

extern crate arrayvec;

use arrayvec::ArrayVec;

fn main() {
    let foo_selectors = vec![0, 1];

    let foos: Vec<_> = foo_selectors
        .into_iter()
        .flat_map(|i| get_foo(i))
        .collect();

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

fn get_foo(i: u8) -> ArrayVec<[u8; 3]> {
    if i % 2 == 0 { [1, 2, 3] } else { [4, 5, 6] }.into()
}

See also:

  • What is the difference between iter and into_iter?
  • How to Iterate Over an Array?
like image 157
Shepmaster Avatar answered Oct 14 '22 07:10

Shepmaster