Skip to content

if let keeps borrows too long #43407

Closed

Description

@oxalica

Borrows in patterns are kept during the whole if let expression, including the else branch, which is counterintuitive and annoying.
For example, this doesn't compile.

let mut x = Some(1);
if let Some(&ref p) = x.as_ref() {
    // do something with p
} else {
    x = Some(2); // error: x is still borrowed
}

To pass the borrow check, you have to write ugly code like this.

let mut x = Some(1);
let mut flag = false;
if let Some(&ref p) = x.as_ref() {
    // do something with p
} else {
    flag = true;
}
if flag {
    x = Some(2);
}

Activity

  1. qnighy commented on Jul 22, 2017

    @qnighy
    Contributor

    This seems to be the known limitation of the current borrow checker. #35132 is one of the similar issues.

  2. oxalica commented on Jul 22, 2017

    @oxalica
    ContributorAuthor

    Unlike match mentioned in #35132, if let with else has two braces and seems to have two different lexical scope naturally, and borrows should only live the first scope.
    On the other hand, although non-lexical lifetimes do fix both issues, I think this one is easier to resolve by simply desugering code like the latter I've shown.

  3. qnighy commented on Jul 22, 2017

    @qnighy
    Contributor

    So you meant to change desugaring of if let, the current behavior of which leads to the very NLL example?
    To me it looks a bit difficult to apply your desugaring in general, since if let can have types other than ().

    Perhaps rust-lang/rfcs#2068 is also related?

  4. codyps commented on Jul 23, 2017

    @codyps
    Contributor

    I don't think desugaring will be an issue. At least handling a return value works with the example below (using fake macro-like syntax, and likely in need of some NoDrop to avoid issues where the expression is Drop)

    if let $p:pattern = $ei:expr {
        $em:expr
    } else {
        $ef:expr
    }
    

    Transformed to:

    {
        let mut flag = false;
        let mut v = if let $p = $ei {
           $em
        } else {
            flag = true;
            unsafe { uninitialized() }
        };
        if flag {
            v = $ef;
        }
        v
    }
    

    A specific example (https://play.rust-lang.org/?gist=205359c0924e50efa8974f1f73250419&version=stable)

    let mut x = Some(1);
    let res = {
        let mut flag = false;
        let mut v = if let Some(&ref p) = x.as_ref() {
            // do something with p
            println!("{}", p);
            p * 3
        } else {
            flag = true;
            unsafe { std::mem::uninitialized() }
        };
        if flag {
            x = Some(2);
            v = 7;
        }
       v
    };
    
  5. added
    A-lifetimesArea: Lifetimes / regions
    C-enhancementCategory: An issue proposing an enhancement or a PR with one.
    on Jul 26, 2017
  6. scottmcm commented on Jul 31, 2017

    @scottmcm
    Member

    There are flagless desugars too, like

    loop {
        {
            if let $p:pattern = $ei:expr {
                break $em:expr
            }
        }
        break $ef:expr
    }

    (Or the nicer version with labeled blocks, if inside the compiler or if that RFC is accepted)

  7. memoryruins commented on Jan 30, 2019

    @memoryruins
    Contributor

    Triage: OP's example now compiles on 2018.
    2015 errors:

    error[E0506]: cannot assign to `x` because it is borrowed
     --> src/main.rs:6:9
      |
    3 |     if let Some(&ref p) = x.as_ref() {
      |                           - borrow of `x` occurs here
    ...
    6 |         x = Some(2); // error: x is still borrowed
      |         ^^^^^^^^^^^ assignment to borrowed `x` occurs here
    

    rustc: 1.32.0

  8. added a commit that references this issue on Mar 11, 2019
    65cb35a
  9. added 3 commits that reference this issue on Apr 22, 2019
    bf0ea60
    c21fbfe
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-lifetimesArea: Lifetimes / regionsC-enhancementCategory: An issue proposing an enhancement or a PR with one.fixed-by-NLLBugs fixed, but only when NLL is enabled.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions