Tracking issue for "Pattern Guards with Bind-By-Move" (rust-lang/rfcs#107)
Steps:
Triage: this has still not been implemented
I keep running into this (or rather, the simpler #14252) in places where I now end up with an extra level of indentation:
match x {
Foo(v) => if do_a {
// ...
} else if do_b {
// ...
} else if do_c {
// ...
} else {
// ...
}
}
when it could be
match x {
Foo(v) if do_a => {
// ...
},
Foo(v) if do_b => {
// ...
},
Foo(v) if do_c => {
// ...
},
Foo(v) => {
// ...
}
}
(i believe this feature is effectively implemented by NLL, namely as PR https://github.com/rust-lang/rust/pull/49870. I should probably port any examples, including corner case examples noted on PR #42088, to the NLL test suite just to make sure.)
Actually perhaps the right thing for me to do would be to put in the feature(bind_by_move_pattern_guards)
and then have that, when NLL is enabled, be the thing that toggles what is currently controlled by -Z disable_ast_check_for_mutation_in_guard
Now that PR #54034 has landed, I'm unassigning myself.
(But, if I understand correctly, the issue should stay open until feature(bind_by_move_pattern_guards)
gets stabilized)
(I guess it would be good to remove the -Zdisable-ast-check-for-mutation-in-guard
flag and replaces uses of it in our test suite with #![feature(bind_by_move_pattern_guards)]
)
unassigning this from myself; I've done the porting to the NLL test suite, but I have higher priority things on my plate to take care of before I go on leave.
(I invite anyone to come and pick up the torch for following through on the remaining steps here!)
since this is implemented and all that remains is 1. making NLL the default everywhere and then 2. stabilizing the feature, I'll let this be handled by T-lang alone now, not T-compiler.
Nominated for next T-lang meeting to see how we want to proceed, specifically re. @pnkfelix's note re. step 1.
In principle I guess stabilizing for Rust 2018 is fine. I'm not sure how much impl complexity that results in -- if nothing else it means more complex branches. I'm a bit wary.
I guess in general I would rather wait until NLL has proceeded further down the stability path to take action here. In particular, I'd like to see NLL enabled on all editions first and without any "outstanding questions", essentially (e.g. the 2PB one).
We discussed this in the language team meeting pre-triage (but not the proper language team meeting; cc @rust-lang/lang). Of note, with respect to https://github.com/rust-lang/rust/issues/15287#issuecomment-451265662, is that NLL has been stabilized on both editions.
Therefore, the participants at the pre-triage (e.g. me, @nikomatsakis, and @pnkfelix) found that we should have "clearance to prep for a stabilization report". This would entail writing up a report (example: https://github.com/rust-lang/rust/pull/57175#issuecomment-450593735) of:
@matthewjasper Since you've been working on things related to this, do you think you could survey the implementation and tests and check whether improvements need to be made in either and then possibly write up a report?
The naive lowering of a bind-by-move pattern with a guard in a match
expression would result in the use of a moved value if the guard returns false. As such we currently emit this error if the user tries to do this:
The error explanation provides a more detailed explanation of the problem.
The checks for match
exhaustiveness assume that the place being matched on is not modified until an arm is chosen, which is currently (mostly) checked with these errors:
Note: The current checks are not sufficient, leading to soundness bugs (see #27282)
These are now handled by careful lowering to MIR so that the MIR borrow checker will check them for soundness. As these errors are unnecessary and occasionally come up, this feature removes them.
The feature gate currently disables the checks for the above errors.
We cannot handle these in the simplest way for the reasons given above. A by value binding in a pattern with a guard is lowered as follows:
For example: let u = match x { v if f(&v) => v, ... }
would become something like
let u;
let __v_ref = &x;
if !f(&*__v_ref) goto next_arm
let v = x;
u = v;
Note: This lowering is observable in how we promote temporaries, how long different borrows can last and in some error messages. I hope to address the error messages soon.
Note: This is already active (emitting warnings), since it catches additional soundness issues to the old check.
To handle the cases in E0301
and E0302
"fake borrows" are added to the parts of the match
scrutinee in the guards. More precisely: a shallow borrow is taken of any place that is:
x.0
in match x { (false, _) if ... }
x
in match x { &(false, _) if ...
or match x { &v if ... }
The borrow lasts from the start of the guard to just after the guard is evaluated successfully. As such if a guard unconditionally diverges, then the borrow will not be active in the guard. A shallow borrow only affects a place and its parents. A shallow borrow of x.0
will prevent mutation of x
and x.0
, but not *(x.0)
or x.0.f
.
As a general observation, most of the tests are using #![feature(nll)]
at the moment, so aren't testing what we would be stabilizing if we want to stabilize this soon.
The tests for this feature are here:
https://github.com/rust-lang/rust/tree/master/src/test/ui/rfc-0107-bind-by-move-pattern-guards
Of particular note is https://github.com/rust-lang/rust/blob/master/src/test/ui/rfc-0107-bind-by-move-pattern-guards/rfc-reject-double-move-across-arms.rs. This test compiles in migrate mode, albeit with a scary warning, and will double free a Vec
.
The following errors are also apparently unnecessary restrictions on patterns. There should probably be a check for why these are errors and whether they can be removed.
@matthewjasper Thanks for the report!
As a general observation, most of the tests are using
#![feature(nll)]
at the moment, so aren't testing what we would be stabilizing if we want to stabilize this soon.
So we would need to adjust some tests then to make sure that stable behavior is exercised.
Of particular note is https://github.com/rust-lang/rust/blob/master/src/test/ui/rfc-0107-bind-by-move-pattern-guards/rfc-reject-double-move-across-arms.rs. This test compiles in migrate mode, albeit with a scary warning, and will double free a
Vec
.
Make the new warnings/errors always be hard errors, or decide that stabilizing unsound code is OK.
Let's not pick the soundness hole solution... ;)
- One solution to this is to treat the errors that are no longer emitted as being AST borrow check errors for the purposes of migrate mode.
So this would mean that we don't introduce new unsound behavior on stable, right?
- Another is to wait for full NLL stabilization.
Given that we've transitioned into NLL on both 2018 & 2015 now, what is your guesstimate as to when we can do full NLL stabilization?
Related work
Let's follow up with fresh issues?
- One solution to this is to treat the errors that are no longer emitted as being AST borrow check errors for the purposes of migrate mode.
So this would mean that we don't introduce new unsound behavior on stable, right?
Correct
- Another is to wait for full NLL stabilization.
Given that we've transitioned into NLL on both 2018 & 2015 now, what is your guesstimate as to when we can do full NLL stabilization?
This year. (1.39 or 1.40)
Cool;
- One solution to this is to treat the errors that are no longer emitted as being AST borrow check errors for the purposes of migrate mode.
So this would mean that we don't introduce new unsound behavior on stable, right?
Correct
@nikomatsakis @pnkfelix Interested to hear what you think about this. My hunch is to say that we should do it assuming we have the tests to show that it works.
We discussed in the lang team meeting -- treating the existing errors as "AST borrowck errors" for purpose of migration mode seems like a good solution, and we are generally in favor of moving forward here once that is done plus test cases are finished.
If anyone wants to work on this then some steps to get started are
feature(bind_by_move_pattern_guards)
and feature(nll)
or compile-flags: -Zborrowck=mir
and remove the nll or -Zborrowck=mir
flag.check_match
query return SignalledError
. If we emit an error, or suppress an error due to bind_by_move_pattern_guards
, we should return SawSomeError
.borrowck
query first check check_match
, and if it returns SawSomeError
, then borrowck should not bother running and just return BorrowCheckResult { signalled_any_error: SawSomeError }
(the other field is removed in #61590)Plan in https://github.com/rust-lang/rust/issues/15287#issuecomment-507054617 was implemented in https://github.com/rust-lang/rust/pull/63059.
Most helpful comment
Plan in https://github.com/rust-lang/rust/issues/15287#issuecomment-507054617 was implemented in https://github.com/rust-lang/rust/pull/63059.