Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I specify a lifetime that is dependent on the borrowed binding of a closure in a separate type?

Tags:

rust

lifetime

I have two types: Lexer and SFunction.

SFunction stands for stateful function and is definined like so:

struct SFunction {
    f: Option<Box<FnMut() -> SFunction>>, 
}

The important part is that any SFunction references a closure that returns an SFunction.

Now I want to have these functions carry state by each affecting the same Lexer. This means that each of these SFunctions has to have a lifetime that depends on a specific Lexer.

Here's some more code if you want to get more of a sense of what I'm doing with this:

impl Lexer {
    fn lex(&mut self) {
        self.sfunction(Lexer::lexNormal).call()
    }

    fn sfunction(&mut self, f: fn(&mut Lexer) -> SFunction) -> SFunction {

        SFunction::new(Box::new(|| f(self)))
        // SFunction { f: Some(Box::new(move ||f(self))) }
    }

    fn lexNormal(&mut self) -> SFunction {
        return SFunction::empty()
    }
}

(Here’s a full version of the code in the Rust playground.)

How do I specify this lifetime requirement in the code?

The compiler errors I'm getting say "cannot infer an appropriate lifetime for capture of self by closure due to conflicting requirements". I'm pretty sure the "conflicting requirements" here is that a Box type assumes the lifetime to be 'static. I could do something like Box<FnMut() -> SFunction + 'a> where 'a is a lifetime defined by the Lexer it depends upon, but I'm not sure how to define such an 'a.

Thanks for your help!

like image 237
Kites Avatar asked Oct 01 '15 04:10

Kites


1 Answers

The problem is in this line:

SFunction::new(Box::new(|| f(self)))

Here, self is a reference to a Lexer, but there's no guarantee that the lexer will live long enough. In fact, it needs to live for the 'static lifetime! Without a lifetime specified, a boxed trait object will use the 'static lifetime. Said in code, these two declarations are equivalent:

<Box<FnMut() -> SFunction>>
<Box<FnMut() -> SFunction> + 'static>

And you can make your code compile (in an unsatisfactory way) by restricting it to accept only references that will live for the 'static lifetime:

fn lex(&'static mut self) {
    self.sfunction(Lexer::lex_normal).call()
}

fn sfunction(&'static mut self, f: fn(&mut Lexer) -> SFunction) -> SFunction {
    SFunction::new(Box::new(move || f(self)))
}

Of course, it's very doubtful that you will have a Lexer with the static lifetime, as that would mean that it's lexing static data, which wouldn't be very useful. That means we need to include lifetimes in your trait object... as you suggested.

Ultimately what helped to see the problem was to restructure your closure a bit:

fn sfunction(&mut self, f: fn(&mut Lexer) -> SFunction) -> SFunction {
    SFunction::new(Box::new(move || {
        // f(self)
        let s2 = self;
        let f2 = f;
        f2(s2)
    }))
}

Compiling this produces an error that points to what seems to be the real problem:

<anon>:31:22: 31:26 error: cannot move out of captured outer variable in an `FnMut` closure [E0507]
<anon>:31             let s2 = self;
                               ^~~~
<anon>:31:17: 31:19 note: attempting to move value to here
<anon>:31             let s2 = self;
                          ^~
<anon>:31:17: 31:19 help: to prevent the move, use `ref s2` or `ref mut s2` to capture value by reference

I believe this is because a FnMut closure may be called multiple times, which would mean that the reference enclosed in the closure would need to be copied around, which would be bad news as &mut references should be unique.

All together, this code works:

struct SFunction<'a> {
    f: Option<Box<FnOnce() -> SFunction<'a> + 'a>>, 
}

impl<'a> SFunction<'a> {
    fn new(f: Box<FnOnce() -> SFunction<'a> + 'a>) -> SFunction<'a> {
        SFunction {
            f: Some(f),
        }
    }

    fn empty() -> SFunction<'a> {
        SFunction {
            f: None,
        }
    }

    fn call(self) { }
}

struct Lexer;

impl Lexer {
    fn lex(&mut self) {
        self.sfunction(Lexer::lex_normal).call()
    }

    fn sfunction(&mut self, f: fn(&mut Lexer) -> SFunction) -> SFunction {
        SFunction::new(Box::new(move || f(self)))
    }

    fn lex_normal<'z>(&'z mut self) -> SFunction<'z> {
        SFunction::empty()
    }
}

fn main() {
    let mut l = Lexer;
    l.lex()
}

I hope my explanation is right and that the changed code still suits your use case!

like image 51
Shepmaster Avatar answered Sep 22 '22 08:09

Shepmaster