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

By-reference pattern guards #2036

Closed
the8472 opened this issue Jun 19, 2017 · 7 comments
Closed

By-reference pattern guards #2036

the8472 opened this issue Jun 19, 2017 · 7 comments
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.

Comments

@the8472
Copy link
Member

the8472 commented Jun 19, 2017

    match Some(vec![1]) {
        Some(v) if !v.is_empty() => { v.into_boxed_slice(); },
        _ => { vec![0; 0].into_boxed_slice(); }
    }

errors with

error[E0008]: cannot bind by-move into a pattern guard
 --> <anon>:3:14
  |
3 |         Some(v) if !v.is_empty() => { v.into_boxed_slice(); },
  |              ^ moves value into pattern guard

The usual workaround is to use Some(ref v) instead. But this prevents the match block from taking ownership.

This forces the guard to be moved into the match, which leads to duplicate code A and B:

    match Some(vec![1]) {
        Some(v) => {
            if !v.is_empty() {
                v.into_boxed_slice(); 
            } else {
                vec![0; 0].into_boxed_slice(); // A
            }
        },
        _ => {
            vec![0; 0].into_boxed_slice(); // B
        }
    }

It would be more ergonomic if moving match arms with guards passed their values by reference to the guard.

@burdges
Copy link

burdges commented Jun 19, 2017

You cannot fix this with (&v).is_empty() either. Could just be deemed a bug in how guards get handled?

@SimonSapin
Copy link
Contributor

I think this is either a bug or a missing feature in the language, depending on your philosophical point of view :)

Each binding in a pattern has one of four binding mode, based on keywords (or on context, with #2005). When matching a value of type T:

  • By mutable reference: ref mut v. In the match arm’s body, v has type &mut T.
  • By shared reference: ref v. In the match arm’s body, v has type &T.
  • By value, mutable: mut v. In the match arm’s body, v has type T and is mutable.
  • By value, immutable: v. In the match arm’s body, v has type T and is immutable.

Currently, the type of v in a guard is the same as in the arm’s body. But passing by value means giving away ownership of the value (for T: !Copy), so it can’t be done twice. So guards are disallowed in the "by value" cases.

We could change the language so that the type in guards is not the same as in the arm’s body in the "by value" cases: mut v and v pattern bindings could cause v in guards to be of type &mut T or &T respectively.

However, now that I’ve typed all this, I realize that this may be a breaking change in the T: Copy case. Ownership is not a problem in these cases, so guards are allowed today so this change would change some types in some existing valid code. We could make v have the different type in guards only if T: !Copy, but that’s kinda weird.

@burdges
Copy link

burdges commented Jun 19, 2017

I only meant if it should be considered a bug that you seemingly cannot use &v in a guard at all, but if you desugar the example into if let expressions in a fairly naively way then it works.

let m = Some(vec![1]);
loop {
    if let Some(v) = m {
        if !v.is_empty() {
            break v.into_boxed_slice();
        }
    }
    break vec![0; 0].into_boxed_slice();
}

I now see &v actually works if v is Copy, but &mut v still fails.

fn foo(v: &mut u32) -> bool { *v+=1; true }

fn main() {
    let mut x = Some(3);
    match x {
        Some(v) if foo(&mut v) => { "0"; },
        _ => { "1"; }
    }
}

@SimonSapin
Copy link
Contributor

Ah, so if I rephrase, an alternative is to not change the type of v in the guard but have the guard and the match arm be the "same scope" and have the usual "use of moved value" errors if the guard moves v away and the match arm also uses it. Yes, I think that could work. (In terms of ownership in theory, I don’t know anything about compiler internals for actually implementing this.)

I now see &v actually works if v is Copy, but &mut v still fails.

Try Some(mut v) in the pattern.

@burdges
Copy link

burdges commented Jun 19, 2017

You still get "error[E0301]: cannot mutably borrow in a pattern guard" even with Some(mut v), presumably because the underlying equivalent desugaring will not give the guard access to the lvalue, so probably the same reason you "cannot bind by-move into a pattern guard".

@nickolay
Copy link

@the8472
Copy link
Member Author

the8472 commented Jan 20, 2019

Yes, it would seem so. Closing as duplicate.

@the8472 the8472 closed this as completed Jan 20, 2019
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

5 participants