Does it safe when there is one writer and multiple readers to write and read usize?

fn main(){
    static mut COUNTER:usize=0;
    static mut TIMES:usize=0;
    const TRY:usize=10000000;
    let t1=thread::spawn(||{
        for x in 0..TRY{
            unsafe{
                TIMES+=1;
                //makes COUNTER keep changing between 0 and usize::MAX
                if COUNTER==0{
                    COUNTER=usize::MAX;
                }else{
                    COUNTER=0;
                }
            }
        }
    });
    let t2=thread::spawn(||{
        for x in 0..TRY{
            unsafe{
                let c=COUNTER;
                if c != 0 && c != usize::MAX{
                    println!("Unexpected value: {}",c);
                }
            }
        }
    });
    t1.join();
    t2.join();
    unsafe{
        assert_eq!(TIMES,TRY);
    }
}

I ran this code, no exception was found. But I wonder whether it is really safe or it just safe on my machine? My CPU architecture is aarch64.

I think if the length of bits of the value is modifying is less than the max bits CPU can process at one time, it is safe. Am I right?

Data races are undefined behavior in Rust by definition, so it's not safe. You can run Miri in the Playground and it will detect the UB in your example. (Miri is under Tools. I set TRY to something more reasonable.)

But playground is also worked well. I knew it maybe UB, but it actually worked "safely".
(Well, maybe TRY seems too scary, but it actually just costs few seconds to run this code on my machine :thinking:)

"Working well" is valid outcome of UB, but it is not guaranteed by definition. Any change to the code or toolchain can break it.

5 Likes

If you try to run Miri with TRY set that high, I'm pretty sure the playground will always timeout. Miri is a few thousand times or so slower than compiled.

In the context of Rust, asking if some piece of unsafe code works safely will be taken to mean, is the code UB free. And the answer in this case is no.

You may be asking, is there some subset of UB where you can rely on the behavior anyway, as with some C compilers. And with rustc, the answer is always a very deliberate no.

At the language level, it's still a data race and UB. At the hardware level, the compiler is allowed to tear a read or write operation even if the hardware could have done it atomically. If you need atomic integer operations, use atomics to get defined behavior (even in the face of a race condition, which is distinct from a data race).

2 Likes

Thanks for explaination. But if that is UB, why static mut exist? What is the correct usage of static mut and when to use it?

In 2021, the only correct usage of it is to machine-translate some single threaded C code into Rust syntax without modifying any semantics. You may never write any static mut by hand.

I think there were some use cases that didn't have alternatives when Rust 1.0 came out. People were pushing to not have it even then, though.

It will likely be deprecated or partially deprecated before too much longer. There's a lot of discussion about how people use it and what you can do as an alternative in that issue.

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.