Explicit lifetimes and nested blocks


#1

Hitting an error with the borrow checker that I just can’t seem to get past. Logically blocks should guarantee that these lifetimes don’t conflict but I’m getting the following error:

struct Foo {
    bar: i32
}

impl Foo {
    fn bar<'a,'b>(&'a mut self) where 'a: 'b {
        {
            let sref: &'b Foo = self;
            println!("{}", sref.bar);
        }
        
        self.bar = 1;
    }
}

fn main() {
    let mut foo = Foo { bar: 0 };
    foo.bar();
}
<anon>:12:9: 12:21 error: cannot assign to `self.bar` because it is borrowed [E0506]
<anon>:12         self.bar = 1;
                  ^~~~~~~~~~~~
<anon>:8:33: 8:37 note: borrow of `self.bar` occurs here
<anon>:8             let sref: &'b Foo = self;
                                         ^~~~
error: aborting due to previous error
playpen: application terminated with error code 101

however if I remove the explicit lifetime on:

            let sref: &Foo = self;

Everything compiles and runs fine. This is a bit contrived example, in the actual code I have 'b is a lifetime for a struct that’s constraining a trait and so I can’t just infer it like in the example above.

Any idea what I’m missing? My mental model thinks that the inner scope would constrain the right lifetime.

-Val


#2

AFAIU lifetime parameters on a function are always assumed to last at least for the duration the function. So by making 'b a lifetime parameter you make it outlive the inner scope you intended it to be.

As for your actual example I don’t know how it would/should look exactly.


#3

Ah ha, that’s the key piece of information I was missing and explains why I was seeing the lifetime last to the end of the function.

I’ll try and update with a bit more pseudo code when I have more than a minute but is there any way that you can have a struct with an explict lifetime that doesn’t last the whole function? I have a struct with a ref that I want to constrain a trait to in the function signature. The compiler won’t let me use _ for the lifetime so I don’t know if what I want to do is possible.


#4

Okay, here’s a bit more involved sample that captures what I’m doing: https://play.rust-lang.org/?gist=eb443c61af3fb66666acb410990bbb24&version=stable

struct Foo<'a> {
    bar: &'a Vec<i32>,
    offset: usize
}

impl<'a> Iterator for Foo<'a> {
    type Item=i32;
    
    fn next(&mut self) -> Option<i32> {
        if self.offset < self.bar.len() {
            let ret = self.bar[self.offset];
            self.offset += 1;
            
            Some(ret)
        } else {
            None
        }
    }
}

fn print<'a,F>(values: &'a mut Vec<i32>, delegate: F) where F: Fn(Foo<'a>) {
    {
        let it = Foo::<'a> {
            bar: values,
            offset: 1
        };
        
        delegate(it);
    }
    
    values.clear();
}

fn main() {
    let mut values: Vec<i32> = vec!(1,2,3,4);

    print(&mut values, move |it| {
        for value in it {
            println!("{}", value)
        }
    });
}

Same error as I was seeing:

<anon>:31:5: 31:11 error: cannot borrow `*values` as mutable because it is also borrowed as immutable [E0502]
<anon>:31     values.clear();
              ^~~~~~
<anon>:24:18: 24:24 note: previous borrow of `*values` occurs here; the immutable borrow prevents subsequent moves or mutable borrows of `*values` until the borrow ends
<anon>:24             bar: values,
                           ^~~~~~
<anon>:32:2: 32:2 note: previous borrow ends here
<anon>:21 fn print<'a,F>(values: &'a mut Vec<i32>, delegate: F) where F: Fn(Foo<'a>) {
          ...
<anon>:32 }
          ^

Basically I want to dispatch to a function/closure of my choosing but using an iterator that provides a view into my data structure. Since I need to constrain the Fn(…) I must use an explicit lifetime. Is there any way to get this to work or is this not the right rusty approach to this?

-Val


#5

If you change the signature of fn print so that the where clause reads

where F: Fn(Foo)

and initialize it with

let it = Foo { ...

(i.e., omit the explicit lifetime references), the code will compile.

My interpretation, which may be simplistic/not quite right, is that explicit lifetime references force the compiler to extend the borrow of values outside of the scope of the function, so placing it in its own block doesn’t terminate the borrow. Conversely, letting the compiler infer the lifetimes does terminate the borrow of values at the end of the inner block, which has the desired effect.


#6

And if you’re wondering how the explicit lifetime would be written, you’d use the higher-rank syntax:

fn print<'a,F>(values: &'a mut Vec<i32>, delegate: F) where F: for<'b> Fn(Foo<'b>) {

#7

Kudos! That’s exactly what I was looking for, separate lifetime that’s not bound to the function scope.

I noticed that higher-rank lifetimes and the fact that explicit lifetimes are bound to function scope aren’t really covered in the docs(just Rustonomicon). I may see if there’s a decent place to insert them into the docs and put together a PR, would have saved me a few days of fighting the borrow checker.

-Val


#8

I would love a page in the book about HRTB; I’ve been working on the second edition, and so haven’t found the time to add to the existing book. It’s the only syntax I know if that isn’t covered, so it’d be awesome to have.


#9

I’m wrapping up a pull request here that adds a quick note in the closures section.

I can add you to the PR if you want, that way I can be sure I have all the nomenclature correct.

-Val


#10

Yes! I will check it out tomorrow.