Repository navigation
"_" patterns and validity invariants #261
Description
Activity
- changed the title
[-]"_" patterns are strange[/-][+]"_" patterns and validity invariants[/+]on Dec 5, 2020 - addedC-open-questionCategory: An open question that we should revisitCategory: An open question that we should revisitA-validityTopic: Related to validity invariantsTopic: Related to validity invariants
on Dec 5, 2020 @Nadrieril points out that
Right now you are allowed to write let (y, _) = x if we moved out x.1 already. The corresponding match is not allowed tho
The corresponding case for partial initialization can currently not be probed since rustc no longer permits writing to individual fields of a not-yet-initialized struct:
fn main() { let x: (i32, bool); x.0 = 5; // this already errors here... let (y, _) = x; // ... so we cannot test if this would be accepted }
If this code was accepted, that would be further evidence for
_patterns inhibiting place-to-value coercions.Like this?
fn main() { let x = (0, String::new()); drop(x.1); let (x, _) = x; // let (x, y) = x; // doesn't compile }
dropdoesn't change the content of the location though, the value still adheres to the validity invariant (and it has to, otherwiseManuallyDropwould be unsound).Reacted by RustyYatoThe following code is not even unsafe, so it certainly cannot be UB:
It was
unsafeback in <=1.21, though. Is it not-unsafe on purpose, or by accident in the MIR unsafety check?Reacted by tesujiThe MIR for
let _ = *p;is empty, so it could well be accidental.Reacted by tesuji and Léo Lanteri ThauvinThis is IMHO even more confusing because
let _ = a;doesn't even dropa, so it feels to me likelet _ = $exprdoesn't discard the expression so the read should actually read.
(on the other hand you could argue that this is because $expr is just removed from MIR, and then it won't drop + it won't read, but that feels less intuitive for someone who doesn't think about IR when writing code)let _ = a; doesn't even drop a
Well, it drops
asometimes...fn mark() {} fn test1(x: Vec<i32>) { let _ = x; // nothing happens here mark(); } fn test2(x: Vec<i32>) { let _ = vec![1]; // this is dropped here mark(); }
Reacted by Mario Carneiro, Elichai Turkel and inquisitivecrystalIn
test2, it isn't the_that drops it. It is the fact that the temporaryvec![1]goes out of scope that drops it.Reacted by tesujiWell, but the
_is what makes it "go out of scope", so I don't think one can entirely separate these two things.One could just as well argue that
let _ = xintest1makes "xgo out of scope".Reacted by Elichai TurkelOne could just as well argue that
let _ = xintest1makes "xgo out of scope".Currently, it does not. But it does in this very similar test using
let _ = {x};instead.Currently, it does not.
Yeah, it doesn't, but by analogy with
let _ = vec![1];, maybe it should.The current situation seems mostly consistent (except for rust-lang/rust#79735), but it's strange because behavior heavily depends on whether the expression on the right evaluate to a temporary or an existing place.
I guess it's for consistency with the fact that
let (y, _) = xonly moves out ofx.0. In that context we don't want_to move anything out@scottmcm also points out that there seems to be a difference between the statements
let _ = *ptr;and*ptr;, which also seems odd at best.Reacted by Léo Lanteri ThauvinThis compiles effectively to
let x = (*ptr).1, so again whatever data is in the second field does not matter. (Note that*ptrhere occurs only as a place expression, not as a value expression, so it makes sense that in(*ptr).1we do not require the second field to be valid.)@RalfJung I think you mean
(*ptr).0here?Ah, yes... I am switching too often between languages that index tuples starting at 0 vs 1. ;)
Reacted by Léo Lanteri Thauvin, Noah Lev, G. Thorondorsen and waffle@pnkfelix points out that adding a type annotations make a difference for the operational behavior of
_patterns... rust-lang/rust#80059 (comment).As I understand things, Rust issue 10488 set the current semantics of
let _ = ...; this comment in particular points out that the lack of binding means that the lifetime of the RHS is governed by the rules of temporaries.Also Cc rust-lang/miri#2360, we probably want to consider some of the code around
_UB that is currently accepted by Miri.With rust-lang/rust#104844 and rust-lang/rust#103208 having landed, the issue is mostly resolved now:
_patterns construct a place but never load from it. That meanslet _ = place;is UB if and only ifaddr_of!(place)is UB, and same for the other ways of using_patterns.There's a bug where this is not quite true for the
!type: rust-lang/rust#117288. But I think that's just a bug, the semantic questions are all clear.Do we have official docs anywhere on the semantics of
_patterns? That seems to be the last missing bit.- addedS-pending-documentationStatus: The issue is "resolved," but this resolution needs documentationStatus: The issue is "resolved," but this resolution needs documentationand removedC-open-questionCategory: An open question that we should revisitCategory: An open question that we should revisit
on Oct 30, 2023
The following code is not even unsafe, so it certainly cannot be UB:
This indicates that the pointer does not really get dereferenced, so the pointed-to data is never interpreted at type
booland thus must not be valid. I am not sure I like this;*ptr(as a value expression!) to me seems like it should conceptually perform the load even if the result is later discarded.Something similar happens here:
This compiles effectively to
let x = (*ptr).0, so again whatever data is in the second field does not matter. (Note that*ptrhere occurs only as a place expression, not as a value expression, so it makes sense that in(*ptr).0we do not require the second field to be valid.)I think this behavior of "_" can be explained by saying that it suppresses the place-to-value coercion, and it is smart enough to even do this in cases like
(x, _)where the coercion is partially suppressed. But I am not sure I like this semantics, it seems rather counter-intuitive to me. It also makes patterns much harder to explain. And it leads to strange special cases such as rust-lang/rust#79735 (I cannot tell if this is some compiler author also being confused by "_", or if the intention is that!is special in that even creating the place is already UB, or something else).