Ian Fiddes
Ian Fiddes

Reputation: 3001

Why are data being mutably borrowed in this iteration over the results of a parallel iterator?

I have the toy example below, where I am iterating over a simple vector of structs and doing a parallel operation on them. After the parallel operation, I want to load all of the results into a sparse matrix.

extern crate rayon;
extern crate sprs;

use rayon::prelude::*;
use sprs::TriMat;

pub struct Data {
    i: usize,
}

fn eval<'a>(d: &Data) -> usize {
    d.i * 2
}

fn main() {
    let data = vec![1, 2, 3, 4];
    let mut recs = Vec::new();
    for x in data {
        let s = Data { i: x };
        recs.push(s);
    }
    let results = recs.par_iter().map(eval);
    let mut matrix = TriMat::new((4, 2));
    results.enumerate().for_each(|(j, scores)| {
        matrix.add_triplet(j, j as usize, 1);
    });
}

The code leads to the error:

error[E0387]: cannot borrow data mutably in a captured outer variable in an `Fn` closure
  --> src/main.rs:26:9
   |
26 |         matrix.add_triplet(j, j as usize, 1);
   |         ^^^^^^
   |
help: consider changing this closure to take self by mutable reference
  --> src/main.rs:25:34
   |
25 |       results.enumerate().for_each(|(j, scores)| {
   |  __________________________________^
26 | |         matrix.add_triplet(j, j as usize, 1);
27 | |     });
   | |_____^

I don't understand how the data are being borrowed mutably.

Upvotes: 0

Views: 62

Answers (1)

Simon Whitehead
Simon Whitehead

Reputation: 65077

It is because map doesn't actually do anything.. it creates a new iterator that is yet to be iterated.

Once we hit the matrix.add_triplet(j, j as usize, 1); code, that iterator is being iterated over but ... its a ParallelIterator ... and therefore the Rust compiler is now concerned about data races.

You've got two options I can see.

First, you can force evaluation of the iterator straight away..:

let results: Vec<_> = recs.par_iter().map(eval).collect();
// ...
results.iter().enumerate() // ...

Or you can wrap access to the matrix in a Mutex (or another sync mechanism):

use std::sync::Mutex;
// ...
let mut matrix = Mutex::new(TriMat::new((4, 2)));
// ...
matrix.lock().unwrap().add_triplet(j, j as usize, 1);

What is best for you I'm not entirely sure given the small sample you've shared with us, but hopefully that gives you an idea of what is happening.

Upvotes: 1

Related Questions