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: .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();
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./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();
Share
Improve this question
edited Mar 17 at 7:40
Dave Shah
asked Mar 17 at 6:32
Dave ShahDave Shah
657 bronze badges
4
|
1 Answer
Reset to default 6Due 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
j
cannot be accessed by multiple threads, it is not shared at all, it is a local variable, each thread has its own copy. The second question is: why do you think that if some variable is mut then there's some safety issue? Rust, unlike other languages, has a built in mechanism that prevents sharing mutable instances (the borrow checker). So you are safe by default, unless you do unsafe Rust. – freakish Commented Mar 17 at 7:26unsafe
keyword). The design of the language itself guarantees that, combined with a pessimistic compiler (refuses to compile if it cannot guarantee that there is no undefined behavior). So if you are afraid of something like multiple threads accessing a variable mutably at the same time, that's undefined behavior and is impossible as long as you do not use theunsafe
keyword. – Finomnis Commented Mar 17 at 21:05