-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[nll] _
patterns should not count as borrows
#53114
Comments
OK, the code is sort of inconsistent right now with respect to what
The explanations for what is going on here in the code are:
I'm not 100% sure why the old borrow check acepts |
Given the chart above, it seems like we could call the current behavior of NLL a kind of "bug fix" when it comes to |
cc @rust-lang/lang -- I'd like opinions on what behavior we think should happen around fn main() {
let foo = (Box::new(22), Box::new(44));
match foo { (_, y) => () }
drop(foo.0); // ok, foo.0 was not moved
} However, we seem to require that you can only This requirement that the match discriminant be valid is actually consistent with NLL's view on matching: NLL views the match desugaring as first borrowing the value that we are going to match upon (with a shared borrow). This borrow persists until an arm is chosen. This prevents match guards from doing weird stuff like mutating the value we are matching on. Requiring the match discriminant to be valid is also consistent with some of the discussions we've had about how to think about exhaustiveness and matches with zero arms like That said I also think that I would also -- personally -- potentially draw a distinction between the "unsafe check" and the checks for what data is moved. I don't think of the unsafe check as a "flow sensitive" check but rather something very simple -- if you access the field, even in dead code, you must be in an unsafe block. Hence I am not too thrilled that |
While I've learned that let _ = mutex.lock().unwrap(); and let _guard = mutex.lock().unwrap(); do fundamentally different things. I expected |
@scottmcm understood — but I think too late to change that. =) Update: (It would also mean that there is no way to extract some fields of a struct without moving the rest... unless we had some other pattern for "don't touch") |
This is what I personally think the table should look like:
My reasoning is this:
I think the way I would implement this in MIR desugaring is:
This is not entirely "backwards compatible", but accepting |
@nikomatsakis so I think the packed field access checking needs to be on MIR (to have access to the final place operations), but raw pointer dereferences and union field accesses could trivially be on HAIR (and we should maybe also move some linting from HIR to HAIR). |
Hmm, that's surprising to me. Maybe I'm forgetting which bits of desugaring are done on HAIR vs MIR -- I would think that the field accesses would be quite easy to spot. @eddyb can you give me an example of where MIR would be better? |
@nikomatsakis Patterns are probably the biggest thing that still exists in HAIR but not MIR. But if we have types in every node then it's probably plausible to handle it. |
@eddyb ah, I see, you mean that we'd have to check in two places basically? (e.g., struct patterns) Seems true, but yes since the patterns themselves are fully explicit and have types also like it's not that hard to handle. |
oh my, what a glorious hack... I'm halfway tempted to give it a whirl... |
Another option: special case matches with a single arm to use the "irrefutable code path": rust/src/librustc_mir/build/matches/mod.rs Lines 268 to 273 in b0297f3
At least I think that's the irrefutable code path... |
The reason there's an unsafety check in MIR is because of the packed-deref check, because I didn't want to track the packed-status of lvalues (e.g., in patterns) in HAIR. EDIT: saw @eddyb said that already.
That (having whether the "irrefutable code path" is used be determined by the number of arms, rather than by let vs. match) looks like a not that terrible idea. |
I think I'll open a separate issue to discuss the unsafefy checker, it seems orthogonal. |
I'm assigning to @matthewjasper since I think that handling this correctly will fall out from the work they've been doing to improve how we formulate the "borrow match values". The TL;DR of that work is that find the set of "place paths" that the match examines:
For each place path, we will ensure that the place path does not change. This is something of a new concept: in particular, if you have e.g. a place path like |
[NLL] Be more permissive when checking access due to Match Partially addresses #53114. notably, we should now have parity with AST borrowck. Matching on uninitialized values is still forbidden. * ~~Give fake borrows for match their own `BorrowKind`~~ * ~~Allow borrows with this kind to happen on values that are already mutably borrowed.~~ * ~~Track borrows with this type even behind shared reference dereferences and consider all accesses to be deep when checking for conflicts with this borrow type. See [src/test/ui/issues/issue-27282-mutate-before-diverging-arm-3.rs](cb5c989#diff-a2126cd3263a1f5342e2ecd5e699fbc6) for an example soundness issue this fixes (a case of #27282 that wasn't handled correctly).~~ * Create a new `BorrowKind`: `Shallow` (name can be bike-shed) * `Shallow` borrows differ from shared borrows in that * When we check for access we treat them as a `Shallow(Some(_))` read * When we check for conflicts with them, if the borrow place is a strict prefix of the access place then we don't consider that a conflict. * For example, a `Shallow` borrow of `x` does not conflict with any access or borrow of `x.0` or `*x` * Remove the current fake borrow in matches. * When building matches, we take a `Shallow` borrow of any `Place` that we switch on or bind in a match, and any prefix of those places. (There are some optimizations where we do fewer borrows, but this shouldn't change semantics) * `match x { &Some(1) => (), _ => (), }` would `Shallow` borrow `x`, `*x` and `(*x as Some).0` (the `*x` borrow is unnecessary, but I'm not sure how easy it would be to remove.) * Replace the fake discriminant read with a `ReadForMatch`. * Change ReadForMatch to only check for initializedness (to prevent `let x: !; match x {}`), but not conflicting borrows. It is still considered a use for liveness and `unsafe` checking. * Give special cased error messages for this kind of borrow. Table from the above issue after this PR | Thing | AST | MIR | Want | Example | | --- | --- | --- | --- |---| | `let _ = <unsafe-field>` | 💚 | 💚 | ❌ | [playground](https://play.rust-lang.org/?gist=bb7843e42fa5318c1043d04bd72abfe4&version=nightly&mode=debug&edition=2015) | | `match <unsafe_field> { _ => () }` | ❌ | ❌ | ❌ | [playground](https://play.rust-lang.org/?gist=3e3af05fbf1fae28fab2aaf9412fb2ea&version=nightly&mode=debug&edition=2015) | | `let _ = <moved>` | 💚 | 💚 | 💚 | [playground](https://play.rust-lang.org/?gist=91a6efde8288558e584aaeee0a50558b&version=nightly&mode=debug&edition=2015) | | `match <moved> { _ => () }` | ❌ | ❌ | 💚 | [playground](https://play.rust-lang.org/?gist=804f8185040b2fe131f2c4a64b3048ca&version=nightly&mode=debug&edition=2015) | | `let _ = <borrowed>` | 💚 | 💚 | 💚 | [playground](https://play.rust-lang.org/?gist=0e487c2893b89cb772ec2f2b7c5da876&version=nightly&mode=debug&edition=2015) | | `match <borrowed> { _ => () }` | 💚 | 💚 | 💚 | [playground](https://play.rust-lang.org/?gist=0e487c2893b89cb772ec2f2b7c5da876&version=nightly&mode=debug&edition=2015) | r? @nikomatsakis
Assigning self and @Centril for work on testing. |
…entril tests encoding current behavior for various cases of "binding" to _. The `_` binding form is special, in that it encodes a "no-op": nothing is actually bound, and thus nothing is moved or borrowed in this scenario. Usually we do the "right" thing in all such cases. The exceptions are explicitly pointed out in this test case, so that we keep track of whether they are eventually fixed. Cc rust-lang#53114. (This does not close the aforementioned issue; it just adds the tests encoding the current behavior, which we hope to eventually fix.)
…entril tests encoding current behavior for various cases of "binding" to _. The `_` binding form is special, in that it encodes a "no-op": nothing is actually bound, and thus nothing is moved or borrowed in this scenario. Usually we do the "right" thing in all such cases. The exceptions are explicitly pointed out in this test case, so that we keep track of whether they are eventually fixed. Cc rust-lang#53114. (This does not close the aforementioned issue; it just adds the tests encoding the current behavior, which we hope to eventually fix.)
…tril tests encoding current behavior for various cases of "binding" to _. The `_` binding form is special, in that it encodes a "no-op": nothing is actually bound, and thus nothing is moved or borrowed in this scenario. Usually we do the "right" thing in all such cases. The exceptions are explicitly pointed out in this test case, so that we keep track of whether they are eventually fixed. Cc rust-lang#53114. (This does not close the aforementioned issue; it just adds the tests encoding the current behavior, which we hope to eventually fix.)
Can this be closed? The original reproducer works now, and it seems like the commits referenced above add tests. |
The tests added still contains code that should compile but doesn't, e.g.
|
That being said, I think this can be changed to P-medium |
@rustbot prioritize Lets have a Zulip thread to talk about why to downgrade this to P-medium. |
Ah, look at this, I'm the one who marked it as P-high and explicitly said that the behavior change for match itself is P-medium @rustbot label: -E-needs-test |
As noted in the table in the issue description, there are essentially two tasks that remain here:
|
@rustbot label: +P-medium -P-high |
@rustbot label: -I-prioritize |
…RalfJung Allow partially moved values in match This PR attempts to unify the behaviour between `let _ = PLACE`, `let _: TY = PLACE;` and `match PLACE { _ => {} }`. The logical conclusion is that the `match` version should not check for uninitialised places nor check that borrows are still live. The `match PLACE {}` case is handled by keeping a `FakeRead` in the unreachable fallback case to verify that `PLACE` has a legal value. Schematically, `match PLACE { arms }` in surface rust becomes in MIR: ```rust PlaceMention(PLACE) match PLACE { // Decision tree for the explicit arms arms, // An extra fallback arm _ => { FakeRead(ForMatchedPlace, PLACE); unreachable } } ``` `match *borrow { _ => {} }` continues to check that `*borrow` is live, but does not read the value. `match *borrow {}` both checks that `*borrow` is live, and fake-reads the value. Continuation of ~rust-lang#102256 ~rust-lang#104844 Fixes rust-lang#99180 rust-lang#53114
Allow partially moved values in match This PR attempts to unify the behaviour between `let _ = PLACE`, `let _: TY = PLACE;` and `match PLACE { _ => {} }`. The logical conclusion is that the `match` version should not check for uninitialised places nor check that borrows are still live. The `match PLACE {}` case is handled by keeping a `FakeRead` in the unreachable fallback case to verify that `PLACE` has a legal value. Schematically, `match PLACE { arms }` in surface rust becomes in MIR: ```rust PlaceMention(PLACE) match PLACE { // Decision tree for the explicit arms arms, // An extra fallback arm _ => { FakeRead(ForMatchedPlace, PLACE); unreachable } } ``` `match *borrow { _ => {} }` continues to check that `*borrow` is live, but does not read the value. `match *borrow {}` both checks that `*borrow` is live, and fake-reads the value. Continuation of ~rust-lang/rust#102256 ~rust-lang/rust#104844 Fixes rust-lang/rust#99180 rust-lang/rust#53114
Historically, we have considered
let _ = foo
to be a no-op. That is, it does not read or "access"foo
in any way. This is why the following code compiles normally. However, it does NOT compile with NLL, because we have an "artificial read" of the matched value (or so it seems):Found in liner-0.4.4.
I believe that we added this artificial read in order to fix #47412, which had to do with enum reads and so forth. It seems like that fix was a bit too strong (cc @eddyb).
UPDATE: Current status as of 2018-10-02
let _ = <unsafe-field>
match <unsafe_field> { _ => () }
let _ = <moved>
match <moved> { _ => () }
let _ = <borrowed>
match <borrowed> { _ => () }
The text was updated successfully, but these errors were encountered: