Does #1 create a reference? Another question is: does #2 cause UB? If yes, where is the relevant rule? I know a relevant rule is
A reference or Box<T> must be aligned and non-null, it cannot be dangling, and it must point to a valid value (in case of dynamically sized types, using the actual dynamic type of the pointee as determined by the metadata). Note that the last point (about pointing to a valid value) remains a subject of some debate.
However, I don't find the rule that says data has invalid value after #0.
Not only it does not create a reference, it does not even constitute a read. Reading into a wildcard is NOP from opsem perspective (Miri won't flag it).
If you read not into a wildcard (e.g. let _foo = *ptr), then the pointer must be valid for reads (i.e. non-null, aligned, points at an allocated object and with correct provenance) but a reference is still not created, which has implications for the aliasing model.
I don't know if this is documented (probably yes) and where, but this is pretty much agreed upon.
Whatever’s left after dropping a value of type T might not be a valid value of type T. In particular, one of the requirements for Box<_> values to be valid is that they must be dereferenceable, but dropping a box deallocates its backing memory (so it might not be dereferenceable anymore). This isn’t a rule, just… a fact or property of Rust, I suppose.
One of the rules for creating a reference to a value is that the value must be valid for its type, as you noted. Therefore, #2 is currently considered UB.
To my understanding, it’s quite possible that #2 will eventually be considered to violate the safety invariant of references (so exposing a reference to an invalid value to arbitrary safe Rust code would be unsound and could cause UB) but not the language-level validity invariant of references (so it would not instantly cause UB, and very careful unsafe code could do it). This is not currently guaranteed, it’s just a future possibility: Should validity of a reference depend on the *contents* of memory in any way? · Issue #414 · rust-lang/unsafe-code-guidelines · GitHub
One of the rules for creating a reference to a value is that the value must be valid for its type, as you noted. Therefore, #2 is currently considered UB.
The question is, I don't find any rule that says that (*ptr).data has an invalid value after #0.
Now: if T is not Copy, this has a very real possibility to be defined to be UB (assuming you do perform a read, and not _ = (*ptr).data), see MIR move elimination by Amanieu · Pull Request #3943 · rust-lang/rfcs · GitHub. If T is Copy, like here, then moving from it does not invalidate it, but drop_in_place()might.
does create a reference a dereference it. Very unsafe.
let _ = *ptr
This is a weird special case in Rust that has no practical use for pointers. Unlike literally any other variable name, this isn't assignment of an expression, it's a match of a pattern that doesn't do anything and the expression is unused. This being a magical no-op used to even accidentally allow unsafe code in safe Rust, because the code was ignored and did nothing (but those bugs have been fixed).
It doesn't make *ptr any safer, but rather it's a useless and unused and whether that has consequences is unclear (if a ptr is dereferenced in a forest and nobody hears it, is it sound?)
I agree that it doesn’t actually create a reference, though my instinct is that it’s equivalent to creating an extremely short-lived immutable/shared reference (and reading from that)…
eh I don’t know.
It’s definitely a different operation in the Abstract Machine than creating a reference. I’m slightly suspicious of the possibility that creating a reference invalidates other pointers/references that otherwise wouldn’t be. I checked the Tree Borrows paper just now, and I’m fairly sure it’s equivalent. I’m also fairly confident that it’s equivalent in Stacked Borrows.
Not every use of *ptr makes a reference, and not every use of this syntax is a read. It can be a place that is used without dereferencing it, e.g. addr_of! and &raw are special.
Reading a pointer via ptr::read is also a special operation that copies bytes without treating it as a reference. It's still unsafe and requires the pointer to be valid, but doesn't have all the extra requirements of safe Rust references around immutability and exclusivity.
However, if you get a value by dereferencing a pointer, it is equivalent of turning the pointer into a reference and using that reference.
I'm pretty sure that's not correct. This is also not lowered into the same MIR. However I'm unable to construct an example where not creating a reference has implications on aliasing (i.e. where the aliasing model will disallow creating a reference but not reading).