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

Inconsistency when borrow-checking 'ref mut' patterns #48495

Closed
Aaron1011 opened this issue Feb 24, 2018 · 3 comments
Closed

Inconsistency when borrow-checking 'ref mut' patterns #48495

Aaron1011 opened this issue Feb 24, 2018 · 3 comments
Labels
A-borrow-checker Area: The borrow checker A-NLL Area: Non-lexical lifetimes (NLL) C-enhancement Category: An issue proposing an enhancement or a PR with one. T-lang Relevant to the language team, which will review and decide on the PR/issue.

Comments

@Aaron1011
Copy link
Member

While investigating #48357, I discovered a strange inconsistency in how 'ref mut' patterns are checked.

This snippet is correctly rejected both with and without NLL (playground):

#![feature(nll)]

enum SimpleFoo {
    Ref(u8)
}

fn bar(foo: SimpleFoo, other_num: &mut u8) {
    match foo {
        SimpleFoo::Ref(ref mut inner) => {
            *inner = *other_num;
        },
    }
}

fn main() {}

It's trying to take a mutable reference to a field of an immutable variable, which clearly should not compile.

However, this snippet compiles both with and without NLL (playground):

#![feature(nll)]

enum SimpleFoo {
    Ref(u8)
}

fn bar(foo: SimpleFoo, other_num: &mut u8) {
    match (0, foo) {
        (0, SimpleFoo::Ref(ref mut inner)) => {
            *inner = *other_num;
        },
        _ => panic!()
    }
}

fn main() {}

The only difference is that foo is now part of a struct - but it now borrow checks. Since foo is consumed by the match, I don't think that this can lead to any soundness issues - but it's still both surprising and inconsistent.

The same issue comes into play in the image crate. This line displays the same inconsistency demonstrated in my example. buffer is an immutable variable, but a mutable reference is taken to the field of one it's variants (which is itself a mutable reference). The ColorType enum takes the place of an integer in the tuple, but the result is the same.

@ExpHP
Copy link
Contributor

ExpHP commented Feb 24, 2018

I do not think this is unusual.

In the first example, you are matching on an lvalue. In the second one, you are matching on an rvalue, which has no sense of immutability. A simpler test for the second example would be match {foo} { ... }.

In the example which compile, the value of foo has effectively been moved out of foo as part of the construction of the rvalue expression, hence why the mutability of foo is irrelevant. You can verify this by adding a let x = foo; line after the match to see if it gives a "use after move" error.

fn bar(foo: SimpleFoo, other_num: &mut u8) {
    match {foo} {
        SimpleFoo::Ref(ref mut inner) => {
            *inner = *other_num;
        },
        _ => panic!()
    }
    let x = foo; // ERROR: use after move
}

// Similar example, with an lvalue
fn bar(foo: SimpleFoo, other_num: &mut u8) {
    match foo {
        // note: mut removed to suppress the other error
        SimpleFoo::Ref(ref inner) => {

        },
        _ => panic!()
    }
    let x = foo; // ok; the match did not move `foo`
}

@ExpHP
Copy link
Contributor

ExpHP commented Feb 24, 2018

I am currently eating my words. match (0, foo) does not move foo, yet permits the construction of a mutable reference.

fn bar(foo: SimpleFoo, other_num: &mut u8) {
    match (0, foo) {
        (0, SimpleFoo::Ref(ref mut inner)) => {
            *inner = *other_num;
        },
        _ => panic!()
    }
    let _ = foo; // This compiles!!
}

I retract my retraction. The difference was in fact between let _ = foo; and let x = foo; (Only the latter is considered a use after move)

@estebank estebank added the A-borrow-checker Area: The borrow checker label Feb 25, 2018
@XAMPPRocky XAMPPRocky added C-enhancement Category: An issue proposing an enhancement or a PR with one. T-lang Relevant to the language team, which will review and decide on the PR/issue. A-NLL Area: Non-lexical lifetimes (NLL) labels May 14, 2018
@nikomatsakis
Copy link
Contributor

This may be surprising, but it's the "expected" behavior from my POV. In particular, what happens is that (0, foo) constructs a tuple -- you are then matching against this tuple, which is a temporary. Temporaries are always considered mutable. This is what allows things like foo.bar().iter_mut() to type-check as well (since the return value of bar() will be stored in a temporary that will then be borrowed for the iter_mut call). Closing as "working as designed, for better or worse".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-borrow-checker Area: The borrow checker A-NLL Area: Non-lexical lifetimes (NLL) C-enhancement Category: An issue proposing an enhancement or a PR with one. T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

5 participants