Search code examples
multithreadingrustparallel-processingborrow-checkercrossbeam

Borrow mutable in a loop


I try to process an array with multiple worker threads, just like Rayon's par_iter() function, but i want to have a mutable reference target, one for each thread.

I have a simple function like this


pub fn parallel_map_vector<A:Sync,T:Send,B:Send>(a:&[A], t:&mut [T], b:&mut [B], f:impl Fn(&A,&mut T)+Send+Sync){
    assert_eq!(a.len(),b.len());
    let atomic_counter = AtomicUsize::new(0);
    crossbeam::scope(|scope| {
        for target in t {
            scope.spawn( |_| {
                loop {
                    let idx = atomic_counter.fetch_add(1, Ordering::Relaxed);
                    if idx < a.len() {unsafe{std::slice::from_raw_parts_mut(b,b_len)};
                        f(&a[idx], target)
                    }else{
                        break
                    }
                }
            });
        }
    }).unwrap();
}

For some reason i keep getting the error

error[E0373]: closure may outlive the current function, but it borrows `target`, which is owned by the current function
  --> htm/src/parallel.rs:11:26
   |
9  |     crossbeam::scope(|scope| {
   |                       ----- has type `&crossbeam::thread::Scope<'1>`
10 |         for target in t {
11 |             scope.spawn( |_| {
   |                          ^^^ may outlive borrowed value `target`
...
17 |                         f(&a[idx], target)
   |                                    ------ `target` is borrowed here
   |
note: function requires argument type to outlive `'1`
  --> htm/src/parallel.rs:11:13
   |
11 | /             scope.spawn( |_| {
12 | |                 loop {
13 | |                     let idx = atomic_counter.fetch_add(1, Ordering::Relaxed);
14 | |                     if idx < a.len() {
...  |
21 | |                 }
22 | |             });
   | |______________^
help: to force the closure to take ownership of `target` (and any other referenced variables), use the `move` keyword
   |
11 |             scope.spawn( move |_| {

but when i try it in rust playground it compiles without problem https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=0a10cbfd2f135975f4682c843664d539 What could be wrong here? The code looks safe to me.


Solution

  • The playground example to which you have linked is using the 2021 edition of Rust, which allows disjoint capture in closures (and hence the closure for each thread captures only target rather than the entire t slice). If you switch to the 2018 edition, it yields the same error as in your question.

    Specify that you require the 2021 edition in your Cargo.toml (requires rustc >= 1.56.0).