Borrow member of struct immutable between threads

I am trying to implement a kind of concurrent Iterator where an array is immutable borrowed between threads but I cannot get around the borrow checker: (Playground)

use std::sync::mpsc::channel;
use threadpool::ThreadPool;
use std::sync::mpsc::Receiver;

const N: i32 = 10;

#[derive(Debug)]
struct ConcurrentIterator {
    array: Vec<i32>,
    threadpool: ThreadPool,
    receiver: Receiver<i32>,
}

impl ConcurrentIterator {
    fn new() -> Self {
        let (tx, receiver) = channel();
        let threadpool = ThreadPool::new(3);
        let array = (0..N).map(|el| el*el).collect::<Vec<i32>>();
        let s = Self {
            array,
            threadpool,
            receiver,
        };
        for i in 0..N {
            let tx = tx.clone();
            let r = &s.array; // array lives as long as Self so this should be possible?
            s.threadpool.execute(move ||{
                let el = &r[i as usize];
                tx.send(*el);
            });
        };
        s
    }
}

impl Iterator for ConcurrentIterator {
    type Item = i32;
    fn next(&mut self) -> Option<Self::Item> {
        match self.receiver.recv() {
            Ok(i) => Some(i),
            Err(_) => None,
        }
    }
}

fn main() {
    let concurrent_iterator = ConcurrentIterator::new();
    for i in concurrent_iterator {
        dbg!(i); // should print 1..=10^2 unordered
    }
}

Error is s.array does not live long enough and cannot move out of s because it is borrowed

There is no guarantee that the thread will finish its job before anything else happens outside the thread's responsibility (including the destruction of values borrowed by the thread). Thus, threading typically requires that values sent into the thread be 'static. This has nothing to do with r outliving s.

Is there a way to do what I want to do?

You could index into the array first and then clone/copy each element individually, instead of trying to perform the array indexing inside the thread.

It's simplified code, in the original I need to index into several elements of the precomputed array (and the indices are difficult to calculate before)

turns out the concept I was looking for is Arc

use std::sync::Arc;
use std::sync::mpsc::channel;
use threadpool::ThreadPool;
use std::sync::mpsc::Receiver;

const N: i32 = 10;

#[derive(Debug)]
struct ConcurrentIterator {
    array: Arc<Vec<i32>>,
    threadpool: ThreadPool,
    receiver: Receiver<i32>,
}

impl ConcurrentIterator {
    fn new() -> Self {
        let (tx, receiver) = channel();
        let threadpool = ThreadPool::new(3);
        let array = Arc::new((0..N).map(|el| el*el).collect::<Vec<i32>>());
        for i in 0..N {
            let tx = tx.clone();
            let array = array.clone();
            threadpool.execute(move ||{
                let el = &array[i as usize];
                tx.send(*el).unwrap();
            });
        };
        Self {
            array,
            threadpool,
            receiver,
        }
    }
}

impl Iterator for ConcurrentIterator {
    type Item = i32;
    fn next(&mut self) -> Option<Self::Item> {
        match self.receiver.recv() {
            Ok(i) => Some(i),
            Err(_) => None,
        }
    }
}

fn main() {
    let concurrent_iterator = ConcurrentIterator::new();
    for i in concurrent_iterator {
        dbg!(i); // should print 1..=10 unordered
    }
}
2 Likes