X-Git-Url: https://git.ralfj.de/web.git/blobdiff_plain/cce7a39d255170e4315c5da78949491e07c1c1f9..59bf25a0c1732f6a1bea9e5777bf9e7d03b8bbb5:/personal/_posts/2024-08-14-places.md?ds=sidebyside diff --git a/personal/_posts/2024-08-14-places.md b/personal/_posts/2024-08-14-places.md index 8f3532c..4b93ee5 100644 --- a/personal/_posts/2024-08-14-places.md +++ b/personal/_posts/2024-08-14-places.md @@ -126,10 +126,16 @@ let _ = *ptr; // This is fine! 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). @@ -145,6 +151,17 @@ match *ptr { _val => "not happy" } // This is UB. 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::(); +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.