Hacker News new | ask | show | jobs
by tialaramex 1845 days ago
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.