X-Git-Url: https://git.ralfj.de/web.git/blobdiff_plain/7a9caa32c818812d46c9eb9696a7082cb37c9941..6a09e54876b302feb4f66e055c39fa4007755a5c:/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 912f7d5..813bfc9 100644 --- a/personal/_posts/2024-08-14-places.md +++ b/personal/_posts/2024-08-14-places.md @@ -11,6 +11,7 @@ However, when it comes to unsafe code, a proper understanding of this dichotomy Consider the following [example](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=9a8802d20da16d6569510124c5827794): ```rust +// As a "packed" struct, this type has alignment 1. #[repr(packed)] struct MyStruct { field: i32 @@ -21,10 +22,12 @@ let ptr = &raw const x.field; // This line is fine. let ptr_copy = &raw const *ptr; // But this line has UB! +// `ptr` is a pointer to `i32` and thus requires 4-byte alignment on +// memory accesses, but `x` is just 1-aligned. let val = *ptr; ``` -Here I am using the unstable but soon-to-be-stabilized "raw borrow" operator, `&raw const`. +Here I am using the unstable but [soon-to-be-stabilized](https://github.com/rust-lang/rust/pull/127679) "raw borrow" operator, `&raw const`. You may know it in its stable form as a macro, `ptr::addr_of!`, but the `&` syntax makes the interplay of places and values more explicit so we will use it here. The last line has Undefined Behavior (UB) because `ptr` points to a field of a packed struct, which is not sufficiently aligned. @@ -123,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).