I've written a piece of code that aims to encode into and decode base64 characters. So far, my code is working, but I've been made aware of the risk of a data race in a previous code of mine, and I believe that is because I've been using mut
variables inside a par_iter()...
block.
I'm asking this question to see if my belief is right. If so, what is the best way to prevent this?
Here is the exact snippet from my code that I think could lead to a race condition.
let i: Vec<u8> = h.into_par_iter().map(|f| {
let l = if f + 8 > e.len() {
e.len()
} else {
f + 8
};
//Citations: https://doc.rust-lang.org/book/ch04-03-slices.html
let st = &e[f..l];
let mut j = 0u8;
for (i, ch) in st.chars().enumerate() {
if ch == '1' {
j |= 1 << (7 - i);
}
}
j
}).collect();
Due to the existence of Send
and Sync
, as well as the borrow checker, it should be impossible to cause a data race in Rust without the use of unsafe
.
In this particular case, no, there is no issue. This is because the mut
variable is created inside the closure, not captured from outside of it. While the closure value is shared between all threads involved in the parallel operation, each execution of the closure creates its own stack frame on the executing thread with its own variables. Therefore, each call creates an independent set of function-local variables, including j
.
This is how normal functions work, too -- a function that declares a local mut
variable is not inherently thread-unsafe because each execution gets its own instance of the local variables.
If you were to capture a mut
variable, you would not be able to modify it from a closure passed to .map
on a parallel iterator, because that closure must implement Fn
. You cannot mutate your captured environment from a Fn
, only from a FnMut
.
This example demonstrates the situation you are worried about (mutating shared state from multiple threads) and how Rust disallows it. Note that v
is declared outside of the closure, not inside -- this means all instances of the closure share a single instance of v
.
use rayon::prelude::*;
fn main() {
let mut v = 0;
[0].into_par_iter().map(|x| {
v += 1;
x
});
}
error[E0594]: cannot assign to `v`, as it is a captured variable in a `Fn` closure
--> src/main.rs:7:9
|
7 | v += 1;
| ^^^^^^ cannot assign