Repository: rust
Issue: atomic::Ordering docs provide fewer guarantees than C++20 reference
Labels: A-docs

### Problem description

The documentation in `std::sync::atomic` and `std::sync::atomic::Ordering` ([source in Rust 1.65](https://github.com/rust-lang/rust/blob/1.65.0/library/core/src/sync/atomic.rs)) describe that the `Ordering`s are equal to those defined in C++20:

In [`std::sync::atomic`](https://doc.rust-lang.org/std/sync/atomic/index.html):

> Each method takes an [`Ordering`](https://doc.rust-lang.org/std/sync/atomic/enum.Ordering.html) which represents the strength of the memory barrier for that operation. These orderings are the same as the [C++20 atomic orderings](https://en.cppreference.com/w/cpp/atomic/memory_order). For more information see the [nomicon](https://doc.rust-lang.org/nomicon/atomics.html).

In [`std::sync::atomic::Ordering`](https://doc.rust-lang.org/std/sync/atomic/enum.Ordering.html):

> Rust’s memory orderings are [the same as those of C++20](https://en.cppreference.com/w/cpp/atomic/memory_order).
> […]
> `Relaxed`: […] Corresponds to [memory_order_relaxed](https://en.cppreference.com/w/cpp/atomic/memory_order#Relaxed_ordering) in C++20.
> `Release`: […] Corresponds to [memory_order_release](https://en.cppreference.com/w/cpp/atomic/memory_order#Release-Acquire_ordering) in C++20.
> `Acquire`: Corresponds to [memory_order_acquire](https://en.cppreference.com/w/cpp/atomic/memory_order#Release-Acquire_ordering) in C++20.
> `AcqRel` […] Corresponds to [memory_order_acq_rel](https://en.cppreference.com/w/cpp/atomic/memory_order#Release-Acquire_ordering) in C++20.
> `SeqCst` […] Corresponds to [memory_order_seq_cst](https://en.cppreference.com/w/cpp/atomic/memory_order#Sequentially-consistent_ordering) in C++20.

However, at the same time, the documentation of `Ordering::Release` and `Ordering::Acquire` ([source in Rust 1.6.5](https://github.com/rust-lang/rust/blob/1.65.0/library/core/src/sync/atomic.rs#L236-L264)) lists the guarantees made by these `Ordering`s. In particular:

> #### [`Release`](https://doc.rust-lang.org/std/sync/atomic/enum.Ordering.html#variant.Release):
> 
> When coupled with a store, all previous operations become ordered before any load of this value with [`Acquire`](https://doc.rust-lang.org/std/sync/atomic/enum.Ordering.html#variant.Acquire) (or stronger) ordering. In particular, all previous writes become visible to all threads that perform an [`Acquire`](https://doc.rust-lang.org/std/sync/atomic/enum.Ordering.html#variant.Acquire) (or stronger) load of this value.
>
> #### [`Acquire`](https://doc.rust-lang.org/std/sync/atomic/enum.Ordering.html#variant.Acquire):
> 
> When coupled with a load, if the loaded value was written by a store operation with [`Release`](https://doc.rust-lang.org/std/sync/atomic/enum.Ordering.html#variant.Release) (or stronger) ordering, then all subsequent operations become ordered after that store. In particular, all subsequent loads will see data written before the store.

These guarantees seem to be less than what the C++20 standard guarantees. Thus the explanation of these orderings differs from the C++20 standard.

Consider the following example, where `a: AtomicUsize`, for example:

One thread does:

```rust
a.store(10, Release);
a.fetch_add(1, Relaxed);
```

A second thread does:

```rust
if a.load(Acquire) == 11 {
    /* … */
}
```

Suppose the second thread reads the value 11 written by the `fetch_add` operation in the first thread. As this value was *not* written by a store operation with `Release` or stronger ordering (but with `Relaxed` ordering), there are no guarantees that thread two will see data written before any store in thread one.

Looking into the [C++20 reference](https://en.cppreference.com/w/cpp/atomic/memory_order), however, reveals a concept named "Release sequences":

> After a *release operation* A is performed on an atomic object M, the longest continuous subsequence of the modification order of M that consists of […] atomic read-modify-write operations made to M by any thread is known as *release sequence headed by A*.

To my understanding, the `fetch_add` in thread one is part of the release sequence headed by the `store`. 

Looking into the [Working Draft N4861](https://open-std.org/JTC1/SC22/WG21/docs/papers/2020/n4861.pdf) of the C++ Standard (the final revision isn't available for free), we see that on page 1525:

> An atomic operation A that performs a release operation on an atomic object M synchronizes with an atomic operation B that performs an acquire operation on M and takes its value from any side effect in the release sequence headed by A.

Thus `a.store` in thread one would synchronize with the `a.load` in thread two, even though the rules described in Rust's documentation of `std::sync::atomic::Ordering` do not allow this reasoning; i.e. following solely the rules in Rust's documenation, one would (wrongly) assume that there is no synchronization between those two threads if thread two reads the value of `11` written by the `fetch_add` operation.

### Additional issues with accessibility

Overall, it's very difficult for someone who starts with the Rust documentation to get an overview on what the Rust atomics really do. The C++20 standard isn't available for free and ~~the linked C++ reference ([C++20 atomic orderings](https://en.cppreference.com/w/cpp/atomic/memory_order)) uses **but does *not* define** the "synchronizes-with" relationship (which is vital for following/understanding the atomic orderings)~~.

### Intent of the Rust documentation

Before making any fixes to the documentation, it should be clarified whether the difference between the Rust documentation and the C++20 reference exists intentionally, i.e. is Rust deliberately giving less guarantees to the programmer than the C++20 standard does? (Even though still following the C++20 memory model in practice "as of now".) Or is the Rust documentation incomplete here?

### Possible improvements

Understanding atomics seems to be very complex, and it might be difficult to fully cover all details in the documentation of `std`. However, the description of the `Ordering`s in Rust's documentation should not differ from those given in the C++ reference.

Possible solutions could be:

* Explicitly state that the guarantees listed under each enum variant of `std::sync::atomic::Ordering` are non-exhaustive (not to be confused with the `non_exhaustive` property of the enum itself) and that the C++20 standard is the normative source.
* Correctly explain the behavior in regard to release sequences.