I have a piece of Rust code where two threads access and modify the same static usize using raw mutable pointers. The operations performed on the value are bitwise and non-atomic, but the logic ensures only one thread writes to the memory location at a time, based on a conditional check.
Here’s the code:
use std::time::Duration;
#[derive(Clone, Copy)]
struct StaticNumber(*mut usize);
unsafe impl Send for StaticNumber {}
fn main() {
let number = StaticNumber(Box::leak(Box::new(usize::MAX)) as *mut usize);
let t1 = std::thread::spawn(move || thread1(number));
let t2 = std::thread::spawn(move || thread2(number));
let _ = t1.join();
let _ = t2.join();
}
fn thread1(number: StaticNumber) {
loop {
if unsafe { *number.0 } & 1 == 0 {
unsafe { *number.0 |= 1 };
} else {
std::thread::sleep(Duration::from_millis(1));
}
}
}
fn thread2(number: StaticNumber) {
loop {
if unsafe { *number.0 } & 1 == 1 {
unsafe { *number.0 &= usize::MAX << 1 };
} else {
std::thread::sleep(Duration::from_millis(1));
}
}
}
The core idea is as follows:
However, the code uses raw pointers to access the same memory concurrently, which is often said to lead to undefined behavior (UB) in Rust due to potential issues like:
The Question:
Given the logic and the context:
While I understand that using atomics would be the correct way to handle this scenario, I am curious if this specific code, with its eventual consistency model, is truly UB in the technical sense. Would love an explanation based on Rust’s aliasing rules, compiler optimizations, and the underlying hardware behavior.
- Is this actually undefined behavior according to Rust’s memory model?
Yes. From std::ptr
:
... it is undefined behavior to perform two concurrent accesses to the same location from different threads unless both accesses only read from memory.
Because of this, questions 2 and 3 are irrelevant; any answer given would be nonsense since UB by definition cannot be reasoned about.