Cannot borrow as mutable with rayon

I have the following code that works fine until I try to use rayon:

use bk_tree::{ BKTree, metrics };
use csv::Reader;
use rayon::prelude::*;

mod col_indices;
mod user_input;

fn main() {
    let input = user_input::args();
    let mut rdr = Reader::from_path(input.file_a).expect("creating csv reader");
    let indices = col_indices::cols(input.file_a_cols);
    let mut tree: BKTree<&str> = BKTree::new(metrics::Levenshtein);
    let mut keys: Vec<String> = Vec::new();
    let mut row = 0;
    
    for result in rdr.records() {
        row += 1;
        let record = result.expect("getting csv record");
        let mut key = "".to_string();
        for i in &indices {
            key.push_str(&record.get(*i).unwrap())
        }
        keys.push(key);
        println!("Row: {}", row)
    }

    keys.iter() // changing to keys.par_iter() breaks the code
       .for_each(|k| tree.add(&k)); 

    let result = tree.find("matt31", 7);
    println!("{:#?}", result.collect::<Vec<_>>()[0].1)
}

If I change to par_iter() it says:

cannot borrow `tree` as mutable, as it is a captured variable in a `Fn` closure

cannot borrow as mutablerustc(E0596)
main.rs(27, 34): cannot borrow as mutable

I've only just started to get my feet wet with rayon so I don't understand why this is happening or how to fix it. Any assistance would be greatly appreciated!! :slightly_smiling_face:

Assuming tree.add() adds an element to the tree, you can't do that in parallel, since different threads would be competing to mutate tree and tree doesn't have any synchronisation around it to prevent data races. The iter() version doesn't have that problem since it does it sequentially. The solution would be to either add some sort of synchronisation to tree (or use a data structure designed to allow safe mutation from multiple threads), or just use the iter() version (in case the overhead of synchronisation is too much).

1 Like

If there is an efficient way to combine two or more BKTrees into one larger BKTree, then you could use rayon::join or ParallelIterator::fold to do this, or you could implement the FromParallelIterator trait to enable keys.par_iter().collect().

However, it looks like the bk_tree crate does not expose any way to do this. If it is possible to do it efficiently, it would probably require changes to the bk_tree crate.

2 Likes

FWIW, this is a great example of Fearless Concurrency (or parallelism), especially because it didn't work. That is, you tried to do something that would not be thread-safe, and the compiler stopped you before it ever became a problem. The flip side is that you can feel more confident when your code does compile!

7 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.