Change:
This example comes from , which in turn was inspired by . The docs imply that they can handle unaligned pointers so long as a reference is never materialized from that pointer (which would be insta-UB). However, running the following program under Miri fails: Here's the failure: : The docs strongly imply that this would be sound. It does this in two ways. First, when contrasting references and raw pointers, it specifically calls out that references need to be aligned: Second, it seems to enumerate the cases in which would be unsound, and operating on unaligned pointers isn't on the list: IMO the docs as currently written are both vague and misleading with respect to what's allowed. It would be good if the docs were more precise on exactly what is and isn't allowed. I was pretty confident in the soundness of the code I'd written, and only discovered the issue thanks to Miri.
Maybe have some thoughts on precise wording that should be used.
That is unfortunate, since when I read them I find them fairly clearly stating this to be not sound. :/ Our mental models must be quite different here. I wonder, if the code did , would you be surprised that the two loads from memory that are happening here are still subject to the usual alignment rules? Your case is really another instance of the same thing -- each of these three addrof!ptrwas evaluated to a place. Sadly there are many inaccurate mental models out there about how place and value expression evaluation interacts. You are by far not the first to be confused by these docs. That's why we the part about being UB -- I would assume that conflicts with your model, so my hope was that people with the wrong model would read that part and then realize they took a wrong turn somewhere. (Or maybe your model makes that UB but allows your code... but then I can't even imagine what your model is like, unfortunately.) I think the proper model here is beautiful and I'd love to teach it to everyone... but maybe, this one time, we don't actually have to. If gets accepted, your code will become sound. will still be UB but I don't think anyone is surprised by that. The "wrong" model isn't actually wrong any more, it is just unnecessarily complicated in my eyes, but it should produce the right predictions. At least I hope so.
I think that any invalid example involving is too easily hand-waved away; null is special-cased for reads so it does not necessarily register as surprising that it is also not allowed here.
Yeah, and I wish I had your mental model! I know what it's like to be on the other side of "I don't see what's confusing about this for you", so I'll try as best as I can to articulate how I interpreted the docs. Maybe I'm missing something, but you said that there are two loads - doesn't that mean that the final dereference (since there are three of them) isn't performing a load? (And, by implication, that final dereference doesn't need to be properly aligned?) Maybe this is the issue: In my mental model, "dereference" doesn't necessarily mean "load". A few examples of why that's my mental model: In C, you can do - that's a dereference but not a load of the referent of (This one I'm less sure about) Also in C, you can do - also a dereference but not a load of the referent In Rust, doing is the way you spell "cast this to a reference", and I've always assumed this doesn't actually perform a load of the referent of ? (though, as the docs mention, materializing an invalid reference is UB, so it's still dangerous for other reasons) For that reason, my mental model of something like is "give me the address that I'd be storing into if I did ". That's reinforced by code like the following: My reading of this example is that, if loaded from , then it would create a temporary, and so the assignment would have no effect. The fact that it has an effect implies to me that is closer to a "place" (I'm using that term loosely to mean "thing I can assign into", not necessarily in its precise Rust meaning). In your example of , I'd assume that is loaded - let's call it - and then (in other words, ) is loaded - let's call it - and then (in other words, ) constructs a place expression but does not result in a load from the memory location addressed by . Honestly, I think I read the docs like this: Yeah I would absolutely love to have the correct model in my head! If I had to speculate, I'd say that part of the disconnect between folks like you - who work on this stuff, and especially its internals - and folks like me - who merely consume these semantics - is that it'd be a lot harder for your mental model to be wrong-but-accidentally-not-causing-any-problems. For me, it's easy to feel like I understand things, and so long as I come to the correct conclusions (where "correct" means something like "write code that other programmers and Miri agree is sound"), I never realize I'm wrong until I come to edge cases like these. By contrast, it'd be a lot harder for you to persist with an incorrect mental model because, when working on the compiler internals, much smaller/finer-grained errors in your model have practical consequences than is the case for me. I think about how confusing it used to be for me to reason about indexing/off-by-one errors/etc when I was first learning to program, and now it's not just something I'm good at, it feels obvious and easy_. It's hard for me to empathize with what it'd be like not to have an intuitive grasp of those concepts. I think this is basically a Hard Problem. I don't really know of any way past it besides a) lots of navel gazing about how you came to your current understanding and, b) trying out your explanations on people (and then seeing if they can expand on the model they've acquired from those explanations, and if those expansions are correct). On a more pragmatic note, I think there are two pieces of low-hanging fruit here that would make things a lot better: Canonical locations for definitions, and more linking between docs. E.g., "all the usual rules" doesn't help me if I don't already know what that refers to. It was only while writing this bullet point that I thought to look and discovered that "place" is a . In the canonical definitions, lists of examples (e.g., this code is sound, this code is unsound, this code is sound, this code is unsound, etc). That gives folks a way of testing their own understanding.
The first sentence is true, but the "by implication" is not. The final dereference produces a place, exactly as you have described, but constructing a place already adds the requirement of being properly aligned, same as you would get if you put a on the front. This is what the example involving was supposed to indicate: the place itself has to be valid, even though a load is not actually happening because it is wrapped in . We found this behavior confusing and foot-gunny as well, which is why documents the new behavior, that constructing a place on its own doesn't add any requirements.
Okay so it sounds like our models are closer than I thought! The one missing bit is that according to it is the deref, not the load, where alignment is checked. I do agree "the usual rules" without further explanation is terrible docs...
Sounds good! A few more clarifying questions: First, do field accesses generate loads? E.g.: Does this expression perform any loads? If not, then under the new model, is this also sound even if is unaligned? Second, I assume that indirecting through a reference generates a load unconditionally? E.g.: I assume that this generates a single load (namely, it loads the value of the (the ) so that it can then calculate the offset of within the type and add that offset to the address stored in )? I assume that, under the new model, can not be unaligned because this generates a load from ?
Under the rules are currently documented and enforced by Miri, loads are irrelevant for alignment. Aligned is checked on ``, i.e. on derefs. This implies that all loaded-from values are aligned, so a separate check at load time is not needed. Under , it is only loads that generate alignment requirements. Field accesses do not require alignment, but they do require "inbounds" ( rules). For the second point, yes -- that code desugars to which desugars to and each load requires alignment. (The first load here is just the load from the local itself which is always aligned since the compiler ensures to allocate it in an aligned way.
applies to a Place expression. A Place can only contain one Deref. Ergo, even if in surface Rust you pile a lot of code into one , you get loads for all but the last (in evaluation order) Deref. The difference with references and raw pointers is that a Deref of a pointer must be explicit, reference Deref may be implicit. I don't know a better way to explain this. I feel like we want to avoid the explanation that a Place can only contain one Deref. That's why your example has a load through the reference, it desugars into multiple Places.
That's not even true for the initially built MIR. (Normalizing to "only one deref" is a MIR pass that runs at some point.) It is certainly not true for something like MiniRust. The thing is that is just omitting a lot of details. pploadaddrof!(load(p))pppload(p)addr_of!(load(load(p)))*loadloadp is allocated sufficiently aligned.
I ran into another issue with , and it's not a big enough deal to open a new issue, so I figured I'd just mention it here. In particular, I have a use case for out-of-bounds projection: using it to compute type layout information based on synthesized pointers. The playground is having issues right now, so here's the code instead. The idea is that I'm trying to figure out the offset of a field within a struct, and I want that to become part of its type information (by assigning to a trait's associated constant). The type is unsized, so I can't construct a so that I actually have an instance of it during const eval. My attempt here synthesizes pointers and does field projection + pointer math to deduce the byte offset. However, this is both unsound and actually rejected by the compiler due to the in-bounds requirement for projection. (As an aside, I'm also not sure about whether this abides by strict provenance. A previous attempt used , and Miri didn't like that. This version - which uses a const reference to start with and converts it to a pointer - seems to be doing better, but maybe I'm missing something.) <details<summaryCode</summary</details
This came up in the t-opsem meeting about whether we should change the rules for and place projection to be "nowrap" rather than "inbounds". However, the general consensus was that solves this problem, so we don't need to go for "nowrap". (The issue with "nowrap" being that it is not currently supported by LLVM, so we need (a) someone to write that patch and (b) a good usecase to convince the LLVM people to take the patch.) Why is not enough in your case? Is it because of unsizedness? Is that more than "just" a feature request for ?
Yeah, sorry, I should have clarified. I saw discussions to this effect, I just wanted to put a "real world" out-of-bounds projection use case out there in case it was useful information. I'm not actually advocating for this, as overall it sounds like the wrong tradeoff to support it given LLVM. Once is stable and supports field offset within unsized types, it would be a valid solution to this problem.
Offsets of the unsized field specifically are not a constant so I don't see how could support them.
I'm referring to the offset to the beginning of an unsized field:
I think that's ok for slices, but does not have statically known alignment.
Given the , I would assume that the offset of in this example is guaranteed to be a function of 's alignment even if is a ? Or should that section only be read as applying to sized types and slice-based DSTs?
The offset of b depends on the dynmic alignment of T, so for dyn Traiy it can differ depending on the underlying dynamic type. This is necessary to ensure that the field is properly aligned.
Hmm that raises an interesting question. IIUC, in practice, the way you would obtain a where happens to be a is that you'd start off with a (for concrete ), and then coerce into where . When you have , you know the alignment of , and you know the field offset of the within . That offset will be a function of 's alignment. However, in a context, how does Rust know what offset the lives at? This must be a dynamic runtime property, which I guess means that, what, the vtable must encode the offset or something wild like that? It does indeed seem to be able to figure it out: <details<summaryDemo code</summaryPrints: </details
The vtable includes the size and alignment of the sole unsized field, which is enough to dynamically compute its offset.
Yes, the code for projecting to a field of unsized type will, at runtime, run to compute the dynamic alignment from the metadata, and compute the offset from that. The codegen logic for this is .

Discussion:
/// as all other references. This macro can create a raw pointer *without* creating /// a reference first. /// <del> /// Note, however, that the `expr` in `addr_of!(expr)` is still subject to all /// the usual rules. In particular, `addr_of!(*ptr::null())` is Undefined /// Behavior because it dereferences a null pointer. </del> <ins> /// The `expr` in `addr_of!(expr)` is evaluated as a place expression, but never loads /// from the place or requires the place to be dereferenceable. This means that /// `addr_of!(*ptr)` is defined behavior even if `ptr` is null, dangling, or misaligned. /// Note however that `addr_of!((*ptr).field)` still requires the projection to /// `field` to be in-bounds, using the same rules as [`offset`]. /// /// Note that `Deref`/`Index` coercions (and their mutable counterparts) are applied inside /// `addr_of!` like everywhere else, in which case a reference is created to call `Deref::deref` or /// `Index::index`, respectively. The statements above only apply when no such coercions are /// applied. /// /// [`offset`]: pointer::offset </ins> /// /// # Example ///

/// as all other references. This macro can create a raw pointer *without* creating /// a reference first. /// <del> /// Note, however, that the `expr` in `addr_of_mut!(expr)` is still subject to all /// the usual rules. In particular, `addr_of_mut!(*ptr::null_mut())` is Undefined /// Behavior because it dereferences a null pointer. </del> <ins> /// The `expr` in `addr_of_mut!(expr)` is evaluated as a place expression, but never loads /// from the place or requires the place to be dereferenceable. This means that /// `addr_of_mut!(*ptr)` is defined behavior even if `ptr` is null, dangling, or misaligned. /// Note however that `addr_of_mut!((*ptr).field)` still requires the projection to /// `field` to be in-bounds, using the same rules as [`offset`]. /// /// Note that `Deref`/`Index` coercions (and their mutable counterparts) are applied inside /// `addr_of_mut!` like everywhere else, in which case a reference is created to call `Deref::deref` /// or `Index::index`, respectively. The statements above only apply when no such coercions are /// applied. /// /// [`offset`]: pointer::offset </ins> /// /// # Examples ///