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

Closure tries to borrow &mut to entire struct, when only a field is required #19004

Closed
mihneadb opened this issue Nov 16, 2014 · 13 comments
Closed
Labels
A-borrow-checker Area: The borrow checker A-closures Area: closures (`|args| { .. }`) C-enhancement Category: An issue proposing an enhancement or a PR with one.

Comments

@mihneadb
Copy link
Contributor

Offending code:

struct Foo {
    b: i32,
    v: Vec<i32>
}

fn incr(x: &mut i32) {
    *x += 1;
}

fn main() {
    let mut f = Foo { b: 0, v: vec![1, 2, 3] };

    f.v.iter().map(|s| {
        incr(&mut f.b);
        println!("{}: {}", f.b, s);
    }).last();
}

Error: error: cannot borrow fas mutable becausef.v is also borrowed as immutable

On playpen: http://is.gd/PTXhJq

Version info:

rustc 0.13.0-nightly (d91a015ab 2014-11-14 23:37:27 +0000)
binary: rustc
commit-hash: d91a015ab4c538f02fbbc03dfb08193a381c2e3b
commit-date: 2014-11-14 23:37:27 +0000
host: x86_64-unknown-linux-gnu
release: 0.13.0-nightly

Thanks @bjz for helping me submit this.

@brendanzab
Copy link
Member

I'm pretty sure that it is fine to reference f.v as immutable and f.b as mutable because they don't touch each other. You can fix this by doing:

fn main() {
    let mut f = Foo { b: 0, v: vec![1, 2, 3] };
    let b = &mut f.b;

    f.v.iter().map(|s| {
        incr(b);
        println!("{}: {}", b, s);
    }).last();
}

It would be nice if Rust was smarter about this though.

@huonw huonw added the A-closures Area: closures (`|args| { .. }`) label Nov 25, 2014
@joshtriplett
Copy link
Member

I just ran into this one today, with 'f' being 'self'; that seems like a rather common case, which will come up any time you write self.somefield.method(|| { ... self.otherfield.method(...) ... }) (and at least one of the methods wants a &mut).

Based on some discussion in the #rust IRC channel, this seems potentially feasible to fix. The closure desugaring just needs to recognize references to fields and capture the field in that case, rather than capturing the entire top-level label.

@Diggsey
Copy link
Contributor

Diggsey commented Apr 25, 2015

Capturing the individual fields rather than "self" would significantly increase the size of the closure. It should be possible to internally capture just the "self" pointer, but make it appear for all intents and purposes that the fields were captured individually.

@steveklabnik
Copy link
Member

Triage: no updates in almost two years.

Personal note: this code still compiles with the original error today. Neat! Not too much pre-1.0 stuff left that does that...

@oli-obk
Copy link
Contributor

oli-obk commented Apr 28, 2017

I don't think we should change this.

  1. There's an easy workaround when you actually want to borrow only a part of the struct.
  2. I actually like this, because it makes me think about what I'm doing.

What should be done imo is to add a suggestion to the error that gives the solution of borrowing the field into a new binding and then using that binding inside the closure.

@Diggsey
Copy link
Contributor

Diggsey commented Apr 28, 2017

@oli-obk That's fine if you're only borrowing one field, but it's a fair amount of boilerplate when you're borrowing say, all but one of the fields in the struct. It also produces a larger closure than necessary, which could be the difference between having to allocate the closure on the heap, vs being able to store it inline, if anyone writes a std::function equivalent for rust.

@oli-obk
Copy link
Contributor

oli-obk commented Apr 28, 2017

@Diggsey ah, so you are suggesting to store the pointer to the struct, but the borrowchecker will still forbid accessing already borrowed fields?

@Diggsey
Copy link
Contributor

Diggsey commented Apr 28, 2017

Yep :)

@joshtriplett
Copy link
Member

That's what I would like to see as well: this would entirely be a change to the borrow checker, with the implementation still just passing the struct pointer (commonly a self pointer) around.

@Mark-Simulacrum Mark-Simulacrum added A-borrow-checker Area: The borrow checker C-enhancement Category: An issue proposing an enhancement or a PR with one. labels Jul 22, 2017
@samsartor
Copy link

samsartor commented Nov 28, 2017

This is fine:

let _a = &mut foo.a;
&mut foo.b; // ok!

So is this:

let _a = &mut foo.a;
loop { &mut foo.b; } // ok!

Why should closures be treated differently? It seems inconsistent with the rest of the language:

let _a = &mut foo.a;
|| &mut foo.b; // cannot borrow `foo` as mutable more than once at a time

Q: Does this require an RFC, or is it minimal and non-breaking enough to be fixed outright?

@Diggsey
Copy link
Contributor

Diggsey commented Nov 28, 2017

this would entirely be a change to the borrow checker

Might have to be careful not to hint to LLVM that this is an exclusive reference?

@arielb1
Copy link
Contributor

arielb1 commented Nov 28, 2017

@samsartor

This is enough of a feature to require an RFC.

@samsartor
Copy link

Working on an RFC now...

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-closures Area: closures (`|args| { .. }`) C-enhancement Category: An issue proposing an enhancement or a PR with one.
Projects
None yet
Development

No branches or pull requests

10 participants