Limit scope of borrow (automagically)?


#1

Hi

I made an attempt to use closures within a function for convenience but the borrow checker struck me down… Code example:

pub fn main() {
    let mut i: u32 = 0;
    let mut f = || {
        i += 1;
    };
    f();
    println!("i = {}", i); 
}

This fails because the closure borrow i for the lifetime of main(). What I imagined (with high hopes) would happen was:

pub fn main() {
    let mut i: u32 = 0;
    let mut f = || {
        i += 1;
    };
    // Borrow of i starts here with f() call
    f();
    // Borrow of i ends here

    // Safe to borrow again.
    println!("i = {}", i); 
}

Technically I don’t see any issues with this… Or?


#2

What you want will be enabled by non-lexical lifetimes, whose stabilization is currently being worked on.

// The only addition:
#![feature(nll)]

pub fn main() {
    let mut i: u32 = 0;
    let mut f = || {
        i += 1;
    };
    f();
    println!("i = {}", i); 
}

Playground link


#3

Thanks for quick reply!! I knew nll was in the works and I’ve been waiting for it for a long time :slight_smile: I wasn’t sure though if this case would also be solved by that.

Awesome!


#4

What’s the minimum nightly needed for this feature?


#5

Even NLL is not so precise. The borrow still starts when the closure is created. The previous example has merely shown how NLL can reason about the “last use” of the mutable borrow.

#![feature(nll)]

pub fn main() {
    let mut i: u32 = 0;
    let mut f = || {
        i += 1;
    };
    println!("i = {}", i);
    f();
}
   Compiling playground v0.0.1 (file:///playground)
error[E0502]: cannot borrow `i` as immutable because it is also borrowed as mutable
 --> src/main.rs:8:24
  |
5 |     let mut f = || {
  |                 -- mutable borrow occurs here
6 |         i += 1;
  |         - previous borrow occurs due to use of `i` in closure
7 |     };
8 |     println!("i = {}", i);
  |                        ^ immutable borrow occurs here
9 |     f();
  |     - borrow later used here

That’s hard to say. One could look for the first nightly that added the feature gate, but chances are it doesn’t actually do anything in that version. It’s a feature under active development.


#6

Maybe defmac can solve your problem. This crate provides a nice way to declare lambda-like macro. With macro, there’s no lifetime error as they’re not even exist on runtime :wink:


#7

This is almost exactly an example in the closures section of the Rust Book, it can be done with regular lexical lifetimes:

fn main() {
    let mut i: u32 = 0;
    {
        let mut f = || {
            i += 1;
        };
        f();
    }
    println!("i = {}", i); 
}

#8

Be careful, I’ve experimented with NLL and found some ICEs using it. That’s why I’ve turned it back off again. So keep in mind that YMMV when using this unstable feature :slight_smile: