Skip to content

MIR-borrowck: behavior around Box<T> is different from AST borrowck #45696

Description

@arielb1

MIR borrowck treats Box<T> differently from AST borrowck (not that AST borrowck is terribly consistent there), for example, in the (simplified) test for #17263:

struct Foo { a: isize, b: isize }

fn main() {
    let mut x: Box<_> = Box::new(Foo { a: 1, b: 2 });
    let (_a, _b) = (&mut x.a, &mut x.b);
    //~^ ERROR cannot borrow `x` (via `x.b`) as mutable more than once at a time
    //~| NOTE first mutable borrow occurs here (via `x.a`)
    //~| NOTE second mutable borrow occurs here (via `x.b`)

    let mut foo: Box<_> = Box::new(Foo { a: 1, b: 2 });
    let (_c, _d) = (&mut foo.a, &foo.b);
    //~^ ERROR cannot borrow `foo` (via `foo.b`) as immutable
    //~| NOTE mutable borrow occurs here (via `foo.a`)
    //~| NOTE immutable borrow occurs here (via `foo.b`)
}
//~^ NOTE first borrow ends here
//~^^ NOTE mutable borrow ends here

AST borrowck reports an error on both lines, but MIR borrowck lets the code pass:

error[E0499]: cannot borrow `x` (via `x.b`) as mutable more than once at a time (Ast)
  --> ../src/test/compile-fail/issue-17263.rs:15:36
   |
15 |     let (_a, _b) = (&mut x.a, &mut x.b);
   |                          ---       ^^^ second mutable borrow occurs here (via `x.b`)
   |                          |
   |                          first mutable borrow occurs here (via `x.a`)
...
25 | }
   | - first borrow ends here

error[E0502]: cannot borrow `foo` (via `foo.b`) as immutable because `foo` is also borrowed as mutable (via `foo.a`) (Ast)
  --> ../src/test/compile-fail/issue-17263.rs:21:34
   |
21 |     let (_c, _d) = (&mut foo.a, &foo.b);
   |                          -----   ^^^^^ immutable borrow occurs here (via `foo.b`)
   |                          |
   |                          mutable borrow occurs here (via `foo.a`)
...
25 | }
   | - mutable borrow ends here

error: aborting due to 2 previous errors

This should be investigated and solved

Activity

  1. nikomatsakis commented on Nov 10, 2017

    @nikomatsakis
    Contributor

    I think we have to decide how we are going to handle Box<T> in MIR borrowck. I would prefer not to introduce hacks into borrowck the way we did in the AST -- if we're going to impose some limitations on Box<T>, I would prefer to do it in the MIR lowering phase (e.g., by lowering let x = &mut box.foo more faithfully into something that uses the DerefMut impl).

    What do you think @arielb1 ? I imagine this will cause more backwards incompatibility on the various corner cases that AST borrow check gets wrong, is one concern.

  2. nikomatsakis commented on Nov 10, 2017

    @nikomatsakis
    Contributor

    An alternative of course is to restore the "full knowledge" to Box<T> -- it's already somewhat special -- with the assumption that we will someday allow user-written types to "opt-in" to such semantics.

  3. arielb1 commented on Nov 16, 2017

    @arielb1
    ContributorAuthor

    @nikomatsakis

    I would prefer to have "full DerefPure" semantics for Box. We already have fairly builtin move semantics, and the AST borrowck semantics are terrible in a few places.

  4. added this to the NLL prototype milestone on Nov 16, 2017
  5. nikomatsakis commented on Nov 16, 2017

    @nikomatsakis
    Contributor

    @arielb1 I agree. It seems like then there is not a lot of work to do here, right? Or are there places where MIR borrowck enforces rules that are too strict?

  6. arielb1 commented on Nov 16, 2017

    @arielb1
    ContributorAuthor

    @nikomatsakis

    I think MIR borrowck currently has an even stupider version of the Box borrow rules, which needs to be removed.

  7. nikomatsakis commented on Nov 17, 2017

    @nikomatsakis
    Contributor

    I'm going to try writing out some tests here in the comments and their current behavior.


    Access field 1 while field 0 is borrowed

    fn main() {
        let mut data = Box::new((1, 2));
        let p = &data.0;
        data.1 += 1;
    }

    This gives an Ast error, but no Mir error, as expected.


    Drop box while field 0 is borrowed

    fn main() {
        let mut data = Box::new((1, 2));
        let p = &data.0;
        drop(data);
        println!("{}", p);
    }
    

    This gives an error in both AST / MIR, as expected.


    Access field 1 when field 0 is borrowed

    fn main() {
        let data = Box::new((format!("Hello"), format!("World")));
        drop(data.0);
        println!("{}", data.1);
    }
    

    This gives an error only in AST, as expected.


    Return reborrowed mutable ref in field 0

    fn return_borrowed<'a>(x: Box<(&'a mut u32, &mut u32)>) -> &'a mut u32 {
        &mut *x.0
    }
    
    fn main() {
    }
    

    This gives an error only in MIR. Seems wrong.

    (Ironically, both AST and MIR seem wrong here. That is, to be consistent, AST should error, but doesn't. And MIR should not, but does.)


  8. nikomatsakis commented on Nov 17, 2017

    @nikomatsakis
    Contributor

    The last error is of course nikomatsakis/nll-rfc#40. I still like the dangly paths answer there, but I do appreciate and understand @arielb1's trepidation. Perhaps we just do some kind of limited variant hard-coded to Box for now. =)

  9. arielb1 commented on Nov 18, 2017

    @arielb1
    ContributorAuthor

    @nikomatsakis

    The last error is of course nikomatsakis/nll-rfc#40

    It is. However, luckily for us, Box is DerefPure, which means that we "known" its destructor can't access the interior (after all, the interior could have been moved), so this looks like a clearer place to special-case.

  10. 20 remaining items

  11. pnkfelix commented on Jul 27, 2018

    @pnkfelix
    Contributor

    (okay now PR #52782 is a change I'm pretty confident in. And as a bonus it also passes the test suite.)

  12. added 2 commits that reference this issue on Aug 2, 2018
  13. added a commit that references this issue on Aug 2, 2018
  14. pnkfelix commented on Nov 6, 2018

    @pnkfelix
    Contributor

    see also some follow-up discussion here #43234 (comment)

  15. added 3 commits that reference this issue on Sep 25, 2019
  16. added a commit that references this issue on Feb 1, 2021
  17. added a commit that references this issue on Feb 1, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

A-NLLArea: Non-lexical lifetimes (NLL)A-borrow-checkerArea: The borrow checkerNLL-completeWorking towards the "valid code works" goal

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions