This is because `my_var` actually denotes a place in memory, and there's multiple things one can do with a place:
one can load the contents of the place from memory (which produces a value), one can create a pointer to the place (which also produces a value, but does not access memory at all),
or one can store a value into this place (which in Rust produces the `()` value, but the side-effect of changing the contents of memory is more relevant).
-Besides local variables, the other main example of a place expression is the result of the `*` operator, which takes a *value* (of pointer type) and turns it into a place.
+Besides local variables, the other main example of a place expression is the result of the `*` operator, which takes a *value* (of pointer type) and turns it into a place.[^deref]
Furthermore, given a place of struct type, we can use a field projection to obtain a place just for that field.
+[^deref]: **Update (2025-12-26):** If you check the [Rust reference](https://doc.rust-lang.org/reference/expressions.html#r-expr.place-value.place-context), you may notice that it actually says that `*` takes a *place* expression. This is a somewhat peculiar Rust design choice related to custom smart pointers implementing the `Deref` trait, and related to borrow checking. That turns out to be easier if you only dereference places. However, when it comes to the operational semantics, the overall picture is much cleaner if we say that `*` works on values.
+
This may sound odd, because it means that `let new_var = my_var;` is not actually a valid statement in our grammar!
To accept this code, the Rust compiler will automatically convert this statement into a form that fits the grammar by adding `load` whenever needed.[^desugar]
`load` takes a place and, as the name indicates, performs a load from memory to obtain the value currently stored in this place.
let _val = *ptr; // This is UB.
```
-The reason for this is that the `_` pattern does *not* incur a place-to-value coercion.
+Note that the grammar above cannot represent this program: in the full grammar of Rust, the `let` syntax is something like "`let` _Pattern_ `=` _PlaceExpr_ `;`",
+and then pattern desugaring decides what to do with that place expression.
+If the pattern is a binder (the common case), a `load` gets inserted to compute the initial value for the local variable that this binder refers to.
+However, if the pattern is `_`, then the place expression still gets evaluated---but the result of that evaluation is simply discarded.
+MIR uses a `PlaceMention` statement to indicate these semantics.
+
+In particular, this means that the `_` pattern does *not* incur a place-to-value coercion!
The desugared form of the relevant part of this code is:
```rust
-let _ = *(load ptr); // This is fine!
+PlaceMention(*(load ptr)); // This is fine!
let _val = load *(load ptr); // This is UB.
```
As you can see, the first line does not actually load from the pointer (the only `load` is there to load the pointer itself from the local variable that stores it).
The scrutinee of a `match` expression is a place expression, and if the pattern is `_` then a value is never constructed.
However, when an actual binder is present, this introduces a local variable and a place-to-value coercion is inserted to compute the value that will be stored in that local variable.
+**Note on `unsafe` blocks.**
+Note that wrapping an expression in a block forces it to be a value expression.
+This means that `unsafe { *ptr }` always loads from the pointer!
+In other words:
+```rust
+let ptr = std::ptr::null::<i32>();
+let _ = *ptr; // This is fine!
+let _ = unsafe { *ptr }; // This is UB.
+```
+The fact that braces force a value expression can occasionally be useful, but the fact that `unsafe` blocks do that is definitely quite unfortunate.
+
### Are there also value-to-place coercions?
So far, we have discussed what happens when a place expression is encountered in a spot where a value expression was expected.