It's not just security. This applies generally where people will shoot at their own feet. In C++ I can apparently have an atomic integer and increment it:
std::atomic<int> n = 0;
n++;
Rust has atomics but they don't work that way.
let n = std::sync::atomic::AtomicU32::new(0);
n.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
Hmm. Why doesn't Rust's "AtomicU32" just implement AddAssign? If it implemented the AddAssign trait then you could at least write:
n += 1
.. and that looks much nicer, it elides all that stuff about memory models, consistency, and... oh...
In the C++ code, the programmer thinks this variable n "is" atomic. It's an atomic integer right? But that's not a thing. C++ is mapping atomic integer operations, which are a thing, onto the type, and not making the integer itself magically atomic, it's just an ordinary (aligned) integer.
So if we tweak both examples to do some slightly trickier arithmetic...
std::atomic<int> n = 0;
std::atomic<int> m = 0;
n++;
m= m + n;
use std::sync::atomic::{AtomicU32, Ordering};
let n = AtomicU32::new(0);
let m = AtomicU32::new(0);
n.fetch_add(1, Ordering::SeqCst);
m.fetch_add(n.load(Ordering::SeqCst), Ordering::SeqCst);
Once again, Rust seems much more verbose, but, wait, actually this isn't the same as the C++. This is probably what the C++ programmer
intended but what they actually
wrote means this:
use std::sync::atomic::{AtomicU32, Ordering};
let n = AtomicU32::new(0);
let m = AtomicU32::new(0);
n.fetch_add(1, Ordering::SeqCst);
m.store(m.load(Ordering::SeqCst) + n.load(Ordering::SeqCst), Ordering::SeqCst);
Well that's just
crazy. Now m can change between when we load from it, and when we add n to it, and then we store back this out-dated value. We definitely didn't want that. But it
looked sane because C++ fools us into believing "Atomic integers" are a thing, which they actually aren't.