rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
95.4k stars 12.29k forks source link

Erroneous borrowck error with early returns #58910

Open nox opened 5 years ago

nox commented 5 years ago

Playground

use syn::spanned::Spanned;
use syn::{Data, DeriveInput, Error, Field, Fields};

// Uncommenting the two lines below trigger a borrowck error.

pub fn struct_fields_mut(
    input: &mut DeriveInput,
) -> Result<impl Iterator<Item = &mut Field>, Error> {
    if let Data::Struct(ref mut data_struct) = input.data {
        //if let Fields::Named(_) = data_struct.fields {
            return Ok(data_struct.fields.iter_mut());
        //}
    }
    Err(Error::new(
        input.span(),
        "only structs can be automatically neutralized",
    ))
}
error[E0502]: cannot borrow `input` as immutable because it is also borrowed as mutable
  --> src/lib.rs:15:9
   |
7  |     input: &mut DeriveInput,
   |            - let's call the lifetime of this reference `'1`
8  | ) -> Result<impl Iterator<Item = &mut Field>, Error> {
9  |     if let Data::Struct(ref mut data_struct) = input.data {
   |                         ------------------- mutable borrow occurs here
10 |         if let Fields::Named(_) = data_struct.fields {
11 |             return Ok(data_struct.fields.iter_mut());
   |                    --------------------------------- returning this value requires that `input.data.0` is borrowed for `'1`
...
15 |         input.span(),
   |         ^^^^^ immutable borrow occurs here
nikomatsakis commented 5 years ago

This, I believe, is the case that we cut out from NLL (what I called "Problem case #3" in my initial blog post) -- specifically, the dreaded "conditionally return borrow". The pattern you have there is:

In that case, with NLL as currently implemented, the borrow (from point A) is extended until the end of the function. This means the borrow is considered ins cope from point A until the end of the function, which in this case includes input.span().

TL;DR: Polonius is supposed to fix this.

nikomatsakis commented 5 years ago

The workaround btw is something like this (playground):

use syn::spanned::Spanned;
use syn::{Data, DeriveInput, Error, Field, Fields};

// Uncommenting the two lines below trigger a borrowck error.

pub fn struct_fields_mut(
    input: &mut DeriveInput,
) -> Result<impl Iterator<Item = &mut Field>, Error> {
    if let Data::Struct(ref data_struct) = input.data {
        if let Fields::Named(_) = data_struct.fields {
            // Repeat the borrow here, where it can never
            // reach the latter use
            if let Data::Struct(ref mut data_struct) = input.data {
                return Ok(data_struct.fields.iter_mut());
            } else {
                panic!()
            }
        }
    }
    Err(Error::new(
        input.span(),
        "only structs can be automatically neutralized",
    ))
}

fn main() { }
Havvy commented 5 years ago

This looks like a duplicate of #21906. If not, it's a duplicate of #51545.