Skip to content
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

Allow *local* mutation and assignments in pattern guards #1006

Closed
Stebalien opened this issue Mar 23, 2015 · 10 comments
Closed

Allow *local* mutation and assignments in pattern guards #1006

Stebalien opened this issue Mar 23, 2015 · 10 comments
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.

Comments

@Stebalien
Copy link
Contributor

Due to rust-lang/rust#15989, it isn't possible to assign/mutate in pattern guards. However, assignment/mutation of variables local to the pattern guard should be perfectly safe.

As reported on reddit: https://pay.reddit.com/r/rust/comments/2ztiag/cannot_mutably_borrow_in_a_pattern_guard/ by qthree:

This code is failing: http://is.gd/cAYb5r But that's ok: http://is.gd/7pF28q

For completeness, the failing code is:

fn main() {
    let v = vec!["1".to_string(), "2".to_string(), "3".to_string()];
    let q = match Some(&v) {
        Some(iv) if iv.iter().any(|x| &x[..]=="2") => true,
        _ => false
    };
}

However, the following works:

fn main() {
    let v = vec!["1".to_string(), "2".to_string(), "3".to_string()];
    let q = match Some(&v) {
        Some(iv) if (|| iv.iter().any(|x| &x[..]=="2"))() => true,
        _ => false
    };
}
@lilyball
Copy link
Contributor

The failing code fails with the following error:

unnamed.rs:4:21: 4:30 error: cannot mutably borrow in a pattern guard [E0301]
unnamed.rs:4         Some(iv) if iv.iter().any(|x| &x[..]=="2") => true,
                                 ^~~~~~~~~
error: aborting due to previous error

But there's no mutation here. Something seems rather confused.

@Stebalien
Copy link
Contributor Author

Stebalien commented Mar 24, 2015 via email

@lilyball
Copy link
Contributor

Oh I suppose it is. I was interpreting it as trying to mutably borrow the iv value, but it's obvious now what's going on.

@durka
Copy link
Contributor

durka commented Feb 4, 2016

It would be really nice to make this check more discriminating. This code (nearly the same as @Stebalien's) shows why it's nonsensical:

#[inline(always)]
fn check<'a, I: Iterator<Item=&'a i32>>(mut i: I) -> bool {
    i.any(|&x| x == 2)
}

fn main() {
    let slice = [1, 2, 3];

    let x = match 42 {
        _ if slice.iter().any(|&x| x == 2) => { true },
        _ if check(slice.iter()) => { true },
        _ => { false }
    };
    println!("{}", x);
}

There's an error when the match checker can "see" the mutable borrow, but when the exact same code is "hidden" away in a function, it's allowed. It's a mutable borrow of a temporary, so there should be no problem.

@aidanhs
Copy link
Member

aidanhs commented Jun 2, 2016

It's a strange error to exist at all given how easy it is to subvert.

use std::cell::Cell;
enum A {
    X(Cell<u8>),
}
fn main() {
    let x = A::X(Cell::new(5));
    match x {
        A::X(ref b) if { b.set(6); b.get() == 6 } => (),
        _ => panic!(),
    }
}

It seems like something that would be better served as a warning if the intention is to prevent accidental mutation in pattern guards?

@comex
Copy link

comex commented Sep 18, 2016

Ping. I'm pretty sure this rule is essentially a compiler deficiency rather than anything with a principled reason to exist. The problems mentioned in #15989 would be solved by treating the expression being matched on as borrowed (in full) during the pattern guard. I suppose this would probably come for free with non-lexical lifetimes?...

cc @pcwalton

@pcwalton
Copy link
Contributor

pcwalton commented Sep 20, 2016

See @pnkfelix' comments in the original PR (#15989). It is something we should be able to relax.

@durka
Copy link
Contributor

durka commented Jan 26, 2018

This just came up again on IRC. Is it something that's actually difficult to implement (so an RFC would require a lot of Detailed Design), or can the proposal simply be "turn off this part of the mutation check"?

@eddyb
Copy link
Member

eddyb commented Jan 26, 2018

@durka It seems like a "MIR borrowck allows us to check complex interaction correctly" kind of issue.
cc @nikomatsakis

@Centril Centril added the T-lang Relevant to the language team, which will review and decide on the RFC. label Jan 27, 2018
@nikomatsakis
Copy link
Contributor

This is more or less a duplicate of rust-lang/rust#27282, rust-lang/rust#31287, etc. We plan to support mutation in match arms, we're working on making it sound. I think I'll just close the issue if that's ok =)

pnkfelix added a commit to pnkfelix/rust that referenced this issue May 29, 2018
Now, if you pass `-Z disable-ast-check-for-mutation-in-guard`, then we
will just allow you to mutably-borrow and assign in guards of `match`
arms.

This is wildly unsound with AST-borrowck. It is also unsound with
MIR-borrowck without further adjustments, which come in later in the
commit series on this Pull Request.

See also rust-lang#24535 and rust-lang/rfcs#1006.
bors added a commit to rust-lang/rust that referenced this issue May 30, 2018
…ake-three, r=nikomatsakis

every match arm reads the match's borrowed input

This PR changes the `match` codegen under NLL (and just NLL, at least for now) to make the following adjustments:
 * It adds a `-Z disable-ast-check-for-mutation-in-guard` which, as described, turns off the naive (conservative but also not 100% sound) check for mutation in guards of match arms.
 * We now borrow the match input at the outset and emit a special `ReadForMatch` statement (that, according to the *static* semantics, reads that borrowed match input) at the start of each match arm. The intent here is to catch cases where the match guard mutates the match input, either via an independent borrow or via `ref mut` borrows in that arm's pattern.
 * In order to ensure that `ref mut` borrows do not actually conflict with the emitted `ReadForMatch` statements, I expanded the two-phase-borrow system slightly, and also changed the MIR code gen so that under NLL, when there is a guard on a match arm, then each pattern variable ends up having *three* temporaries associated with it:
   1. The first temporary will hold the substructure being matched; this is what we will move the (substructural) value into *if* the guard succeeds.
   2. The second temporary also corresponds to the same value as the first, but we are just constructing this temporarily for use during the scope of the guard; it is unaliased and its sole referrer is the third temporary.
   3. The third temporary is a reference to the second temporary.
   * (This sounds complicated, I know, but its actually *simpler* than what I was doing before and had checked into the repo, which was to sometimes construct the final value and then take a reference to it before evaluating the guard. See also PR #49870.)

Fix #27282

This also provides a path towards resolving #24535 aka rust-lang/rfcs#1006, at least once the `-Z disable-ast-check-for-mutation-in-guard` is just turned on by default (under NLL, that is. It is not sound under AST-borrowck).
 * But I did not want to make `#![feature(nll)]` imply that as part of this PR; that seemed like too drastic a change to me.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

No branches or pull requests

10 participants