1

I am trying to make a prime sieve in Rust.

I need several mutable references to the same element of the array, not mutable references to different parts of the array.

For the way this works, I know data races are not a relevant problem, so multiple mutable references are acceptable, but the Rust compiler does not accept my unsafe code.

I am using crossbeam 0.8.0.

fn extend_helper( primes: &Vec<usize>, true_block: &mut Vec<bool>, segment_min: usize, segment_len: usize, ) { crossbeam::scope(|scope| { for prime in primes { let prime = prime.clone(); let segment_min = &segment_min; let segment_len = &segment_len; let shared = unsafe { &mut *true_block }; scope.spawn(move |_| { let tmp = smallest_multiple_of_n_geq_m(prime, *segment_min) - *segment_min; for j in (tmp..*segment_len).step_by(prime) { shared[j] = false; } }); } }) .unwrap(); } fn smallest_multiple_of_n_geq_m(n: usize, m: usize) -> usize { m + ((n - (m % n)) % n) } 
error[E0499]: cannot borrow `*true_block` as mutable more than once at a time --> src/lib.rs:12:35 | 7 | crossbeam::scope(|scope| { | ----- has type `&Scope<'1>` ... 12 | let shared = unsafe { &mut *true_block }; | ^^^^^^^^^^^^^^^^ `*true_block` was mutably borrowed here in the previous iteration of the loop 13 | 14 | / scope.spawn(move |_| { 15 | | let tmp = smallest_multiple_of_n_geq_m(prime, *segment_min) - *segment_min; 16 | | for j in (tmp..*segment_len).step_by(prime) { 17 | | shared[j] = false; 18 | | } 19 | | }); | |______________- argument requires that `*true_block` is borrowed for `'1` warning: unnecessary `unsafe` block --> src/lib.rs:12:26 | 12 | let shared = unsafe { &mut *true_block }; | ^^^^^^ unnecessary `unsafe` block | = note: `#[warn(unused_unsafe)]` on by default 

How I should write the unsafe in a way that Rust accepts?

5

1 Answer 1

4

Rust does not allow that in its model. You want AtomicBool with relaxed ordering.

This is a somewhat common edge case in most concurrency models - if you have two non-atomic writes of the same value to one location, is that well-defined? In Rust (and C++) it is not, and you need to explicitly use atomic.

On any platform you care about, the atomic bool store with relaxed ordering will have no impact.


For a reason why this matters, consider:

pub fn do_the_thing(x: &mut bool) { if !*x { return }; // Do some stuff. *x = true; } 

At setting x to true, the compiler is free to assume (due to no shared mutable references) that x is still false. It may implement this assignment as, e.g., inc x in assembly, moving its value from 0 to 1.

If two threads came through this and both reached *x = true, its value may become something other than 0 or 1, which could violate other assumed invariants.

Sign up to request clarification or add additional context in comments.

8 Comments

Using shared[j].store(false, Ordering::Relaxed) It will block the thread in some way like Mutex?
@Delfin: No, this is an atomic variable. I suggest brushing up on concurrency and threading primitives when you have a chance, as you dive into such code. An atomic acts like a regular variable wrapped in a mutex, so only one thread can modify it at a time - except implemented in hardware so its much faster. If you observe the assembly output, it produces identical output as your regular boolean store, due to relaxed ordering constraints. Generally, you want to leave the ordering constraint as SeqCst if you don't know what to do, but in this specific case Relaxed is fine.
And how i convert my Vec<bool> to &[AtomicBool]?
@Delfin Well, frankly that's a very basic question. I highly suggest you get some more practice with Rust (via the Rust book) before diving into such complicated code. You can search around for "vec slices" to learn more.
@Delfin The suggestion is to use Vec<AtomicBool> to begin with, and then you get &[AtomicBool] by calling as_slice() or just applying & to the vec.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.