# Why multiple mutable reference even without data access is UB?

**URL:** <https://users.rust-lang.org/t/why-multiple-mutable-reference-even-without-data-access-is-ub/118152>\
**Category:** help\
**Created:** [September 27, 2024, 5:19am UTC](https://users.rust-lang.org/t/why-multiple-mutable-reference-even-without-data-access-is-ub/118152 "2024-09-27T05:19:07Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![fangzhen](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/fangzhen/32/43316_2.png) [@fangzhen](https://users.rust-lang.org/u/fangzhen)\
**Post date:** [September 27, 2024, 5:19am UTC](https://users.rust-lang.org/t/why-multiple-mutable-reference-even-without-data-access-is-ub/118152/1 "2024-09-27T05:19:08Z")

</div>

Rust 2024 has make &mut STATIC\_mut a hard error. I'm trying to understand why the alternative (e.g. `&UnsafeCell`) is safer than `static mut`. And I think one key reason is that multiple mutable reference (even without data access) is UB:

Quoting from [Disallow \*references\* to `static mut` [Edition Idea] · Issue #114447 · rust-lang/rust · GitHub](https://github.com/rust-lang/rust/issues/114447#issuecomment-2137998517)

> the differences are in whether you get a _reference_ directly. `*mut`s are allowed to alias, so you can make new ones all day long and keep them around -- the only problems are when you actually read or write through them. Whereas multiple independent live `&mut`s at the same time is UB _even without a data race_. So that's the difference in footgun-ness.

And quoting [Consider deprecation of UB-happy `static mut` · Issue #53639 · rust-lang/rust · GitHub](https://github.com/rust-lang/rust/issues/53639#issuecomment-415480483)

> With `unsafe impl Sync`, you only have to prove the _data accesses_ correct, but with `static mut`, the _references themselves_ can conflict.  
> **EDIT** : Now if "references exist" cannot be UB, _ever_, then this is not a problem, but IIUC, they do.

In multiple mutable reference with data access, there will be races which causes UB.  
However, how does UB happen with just multiple references themselves?

---

<div class="post-metadata">

**Author:** ![SkiFire13](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/skifire13/32/17959_2.png) [@SkiFire13](https://users.rust-lang.org/u/SkiFire13)\
**Post date:** [September 27, 2024, 5:58am UTC](https://users.rust-lang.org/t/why-multiple-mutable-reference-even-without-data-access-is-ub/118152/2 "2024-09-27T05:58:49Z")

</div>

> [@fangzhen](#):
>
> However, how does UB happen with just multiple references themselves?

UB happens when an invalid state in a program is reached, and having multiple mutable references is exactly that.

The compiler is free to assume that two mutable references do not alias (unless one was derived/reborrowed from the other) and optimize accordingly.

---

<div class="post-metadata">

**Author:** ![quinedot](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/quinedot/32/17693_2.png) [@quinedot](https://users.rust-lang.org/u/quinedot)\
**Post date:** [September 27, 2024, 6:07am UTC](https://users.rust-lang.org/t/why-multiple-mutable-reference-even-without-data-access-is-ub/118152/3 "2024-09-27T06:07:23Z")

</div>

> [@fangzhen](#):
>
> However, how does UB happen with just multiple references themselves?

The glib answer is that UB is a language-level concept, and the language says so.

For one practical possibility, consider that if you had for example...

```rust
fn one() {
     let exclusive_i_claim = unsafe { &mut GLOBAL };
     let read = *exclusive_i_claim;
     two();
     let _still_alive = exclusive_i_claim;
     use_the_value(read);
}

```

...the compiler may assume `read` hasn't changed. But if `two` writes to `GLOBAL`, that would be incorrect.

But note also that the example is just one simple possibility amongst unenumerable others. Compilers are allowed to do literally anything when UB is encountered, leading to many unpredictable emergent miscompilations. (The days of "predictable lowering to machine code" and the like are 2+ decades behind us.)

---

<div class="post-metadata">

**Author:** ![fangzhen](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/fangzhen/32/43316_2.png) [@fangzhen](https://users.rust-lang.org/u/fangzhen)\
**Post date:** [September 27, 2024, 9:19am UTC](https://users.rust-lang.org/t/why-multiple-mutable-reference-even-without-data-access-is-ub/118152/4 "2024-09-27T09:19:24Z")

</div>

> [@SkiFire13](#):
>
> The compiler is free to assume that two mutable references do not alias (unless one was derived/reborrowed from the other) and optimize accordingly.

> [@quinedot](#):
>
> Compilers are allowed to do literally anything when UB is encountered, leading to many unpredictable emergent miscompilations.

In theory, I believe so (that compiler could do anything when UB). And, in real world, compiler tends to do something useful, like optimization.

Then I'm curious about what difference/optimization a compiler could practically make when multiple reference but _without_ data access is UB.  
If I understand correct, the example given by @quinedot shows what the compiler could do when multiple reference _with_ data access is UB. However, I can’t seem to think of any specific optimizations that would apply in the case where there’s no data access.

---

<div class="post-metadata">

**Author:** ![tczajka](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/tczajka/32/24743_2.png) [@tczajka](https://users.rust-lang.org/u/tczajka)\
**Post date:** [September 27, 2024, 9:58am UTC](https://users.rust-lang.org/t/why-multiple-mutable-reference-even-without-data-access-is-ub/118152/5 "2024-09-27T09:58:02Z")

</div>

> [@fangzhen](#):
>
> However, I can’t seem to think of any specific optimizations that would apply in the case where there’s no data access.

It allows speculative and out of order reads and writes.

---

<div class="post-metadata">

**Author:** ![farnz](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/farnz/32/14366_2.png) [@farnz](https://users.rust-lang.org/u/farnz)\
**Post date:** [September 27, 2024, 10:00am UTC](https://users.rust-lang.org/t/why-multiple-mutable-reference-even-without-data-access-is-ub/118152/6 "2024-09-27T10:00:12Z")

</div>

As an example, the compiler is permitted to replace taking a mutable reference with loading from the place referred to, all (if any) operations on the dereference of the mutable reference with operations on the loaded value, and the destruction of the mutable reference with a write back to the original place.

This can be an optimization if there's not too much register pressure, because it means that you get to schedule the load very early on, and then operate entirely in registers; however, if it's done to two functions with mutable references to the same place, it converts a previously innocuous looking function into a data race.

---

<div class="post-metadata">

**Author:** ![afetisov](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/afetisov/32/22736_2.png) [@afetisov](https://users.rust-lang.org/u/afetisov)\
**Post date:** [September 27, 2024, 2:02pm UTC](https://users.rust-lang.org/t/why-multiple-mutable-reference-even-without-data-access-is-ub/118152/7 "2024-09-27T14:02:35Z")

</div>

If you're not doing any data accesses, then why are you taking those references in the first place?

Most likely, you're not doing data access _at this specific time_. Or, you're accessing only part of the referenced memory, and don't access some other part. Or you don't access it yourself, but pass it to some other function which may do accesses.

In all of those cases, it's not that hard to imagine how optimizations could mess up your code if you violate the non-aliasing assumptions. The compiler may inline functions, move code around, load & cache data over function calls, erase writes which are non-observable according to its aliasing assumptions. It can load bigger regions of memory than you specified, because it may be faster (e.g. allows to load several struct fields in a single SIMD load). Similarly, it can write to bigger regions of memory, if it knows that those spurious writes are non-observable.

Technically, simply creating a `&mut T` provides non-aliasing guarantees to LLVM, so it's free to insert spurious loads and stores to that location, as long as the observable program behaviour is the same. In practice, it's obviously unlikely to do it if you don't do any accesses, but it can be hard to guarantee that. Creating overlapping `&mut` references that are really not used in any way at all is such a useless edge case that no one will bother to complicate the memory model just for that.

---

<div class="post-metadata">

**Author:** ![kornel](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/kornel/32/4823_2.png) [@kornel](https://users.rust-lang.org/u/kornel)\
**Post date:** [September 27, 2024, 2:48pm UTC](https://users.rust-lang.org/t/why-multiple-mutable-reference-even-without-data-access-is-ub/118152/8 "2024-09-27T14:48:45Z")

</div>

> [@SkiFire13](#):
>
> UB happens when an invalid state in a program is reached

This could be misunderstood as UB being an event. In practice UB is an _assumption_ that the implementation is allowed to make. This is why consequences of UB are so hard to define, because the assumption that `&mut` is unique can be implicit in lots of places in the optimizer, creating all kinds of paradoxes when the assumption is violated.

---

<div class="post-metadata">

**Author:** ![afetisov](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/afetisov/32/22736_2.png) [@afetisov](https://users.rust-lang.org/u/afetisov)\
**Post date:** [September 27, 2024, 3:57pm UTC](https://users.rust-lang.org/t/why-multiple-mutable-reference-even-without-data-access-is-ub/118152/9 "2024-09-27T15:57:20Z")

</div>

From the operational semantics perspective, it's an event. That's why Miri is able to check for UB: it runs the code in an interpreter, and checks that it doesn't perform any invalid operations. My understanding is that turning cases of UB from a vague assumption into a checkable event is an important part of opsem work.

---

<div class="post-metadata">

**Author:** ![farnz](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/farnz/32/14366_2.png) [@farnz](https://users.rust-lang.org/u/farnz)\
**Post date:** [September 27, 2024, 4:10pm UTC](https://users.rust-lang.org/t/why-multiple-mutable-reference-even-without-data-access-is-ub/118152/10 "2024-09-27T16:10:57Z")

</div>

> [@fangzhen](#):
>
> In theory, I believe so (that compiler could do anything when UB). And, in real world, compiler tends to do something useful, like optimization.

In this model, I think it's important to understand how modern optimizing compilers actually work.

Modern optimizing compilers start by translating your program from source code into an Intermediate Representation (IR), whose semantics are not guaranteed to be the same as the source language. This translation is guaranteed\[1\] to preserve the defined behaviours of the source language.

Then, the compiler applies a series of transforms to the IR that are guaranteed to preserve the semantics of the IR, but that are in some sense making it better. We call these transforms "optimizations", and we use cost models to have a sense of what the "best" form of the IR was.

Then, we go down one of two paths:

1. Translate the current IR into a different IR, preserving defined behaviours, and go back to applying transforms, just on a new IR.
2. Translate this IR into machine code instead of a new IR.

The new IR is usually "lower level" in some sense; for example, the last time I looked at it, `rustc` translates into HIR, then MIR, then LLVM IR, then into machine-specific IR, then to machine code, and optimizations can take place at every level.

However, this has a nasty implication; because UB (definitionally) means that the translation from source to IR doesn't preserve the intended meaning of the code (since UB says "this code _has_ no semantic meaning), the translation output can be completely different to the source.

Further, because the optimizing transforms are done at the IR level, the transform may not even be sensible if you think purely about the source code and machine code; for a trivial example, the LLVM IR [LCSSA transform](https://llvm.org/docs/Passes.html#lcssa-loop-closed-ssa-form-pass) cannot exist in Rust source code, since Rust doesn't have phi nodes, and by the time you get to machine code, phi nodes are gone.

* * *

1. Guarantee in this sense meaning "if the translation does not preserve all the defined behaviours, the compiler is buggy and will be fixed".

---

<div class="post-metadata">

**Author:** ![system](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/system/32/51372_2.png) [@system](https://users.rust-lang.org/u/system)\
**Post date:** [December 26, 2024, 4:11pm UTC](https://users.rust-lang.org/t/why-multiple-mutable-reference-even-without-data-access-is-ub/118152/11 "2024-12-26T16:11:11Z")

</div>

Closed on December 26, 2024, 4:11pm UTC

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.
