How to specify a lifetime for an Option?

2020-07-07 04:06发布

问题:

I'm trying to put a field on a struct that should hold an Option<closure>.

However, Rust is yelling at me that I have to specify the lifetime (not that I would have really grokked that yet). I'm trying my best to do so but Rust is never happy with what I come up with. Take a look at my inline comments for the compile errors I got.

struct Floor{
    handler: Option<|| ->&str> //this gives: missing lifetime specifier 
    //handler: Option<||: 'a> // this gives: use of undeclared lifetime name `'a`
}

impl Floor {
    // I guess I need to specify life time here as well 
    // but I can't figure out for the life of me what's the correct syntax
    fn get(&mut self, handler: || -> &str){
        self.handler = Some(handler);
    }
}

回答1:

This gets a bit trickier.

As a general rule of thumb, whenever you're storing a borrowed reference (i.e., an & type) in a data structure, then you need to name its lifetime. In this case, you were on the right track by using a 'a, but that 'a has to be introduced in the current scope. It's done the same way you introduce type variables. So to define your Floor struct:

struct Floor<'a> {
    handler: Option<|| -> &'a str>
}

But there's another problem here. The closure itself is also a reference with a lifetime, which also must be named. So there are two different lifetimes at play here! Try this:

struct Floor<'cl, 'a> {
    handler: Option<||:'cl -> &'a str>
}

For your impl Floor, you also need to introduce these lifetimes into scope:

impl<'cl, 'a> Floor<'cl, 'a> {
    fn get(&mut self, handler: ||:'cl -> &'a str){
        self.handler = Some(handler);
    }
}

You could technically reduce this down to one lifetime and use ||:'a -> &'a str, but this implies that the &str returned always has the same lifetime as the closure itself, which I think is a bad assumption to make.



回答2:

Answer for current Rust version 1.x:

There are two possibilities to get what you want: either an unboxed closure or a boxed one. Unboxed closures are incredibly fast (most of the time, they are inlined), but they add a type parameter to the struct. Boxed closures add a bit freedom here: their type is erased by one level of indirection, which sadly is a bit slower.

My code has some example functions and for that reason it's a bit longer, please excuse that ;)

Unboxed Closure

Full code:

struct Floor<F>
    where F: for<'a> FnMut() -> &'a str 
{
    handler: Option<F>,
}


impl<F> Floor<F> 
    where F: for<'a> FnMut() -> &'a str
{
    pub fn with_handler(handler: F) -> Self {
        Floor {
            handler: Some(handler),
        }
    }

    pub fn empty() -> Self {
        Floor {
            handler: None,
        }
    }

    pub fn set_handler(&mut self, handler: F) {
        self.handler = Some(handler);
    }

    pub fn do_it(&mut self) {
        if let Some(ref mut h) = self.handler {
            println!("Output: {}", h());
        }
    }
}

fn main() {
    let mut a = Floor::with_handler(|| "hi");
    a.do_it();

    let mut b = Floor::empty();
    b.set_handler(|| "cheesecake");
    b.do_it();
}

Now this has some typical problems: You can't simply have a Vec of multiple Floors and every function using a Floor object needs to have type parameter on it's own. Also: if you remove the line b.set_handler(|| "cheesecake");, the code won't compile, because the compiler is lacking type information for b.

In some cases you won't run into those problems -- in others you'll need another solution.

Boxed closures

Full code:

type HandlerFun = Box<for<'a> FnMut() -> &'a str>;

struct Floor {
    handler: Option<HandlerFun>,
}

impl Floor {
    pub fn with_handler(handler: HandlerFun) -> Self {
        Floor {
            handler: Some(handler),
        }
    }

    pub fn empty() -> Self {
        Floor {
            handler: None,
        }
    }

    pub fn set_handler(&mut self, handler: HandlerFun) {
        self.handler = Some(handler);
    }

    pub fn do_it(&mut self) {
        if let Some(ref mut h) = self.handler {
            println!("Output: {}", h());
        }
    }
}

fn main() {
    let mut a = Floor::with_handler(Box::new(|| "hi"));
    a.do_it();

    let mut b = Floor::empty();
    b.set_handler(Box::new(|| "cheesecake"));
    b.do_it();
}

It's a bit slower, because we have a heap allocation for every closure and when calling a boxed closure it's an indirect call most of the time (CPUs don't like indirect calls...).

But the Floor struct does not have a type parameter, so you can have a Vec of them. You can also remove b.set_handler(Box::new(|| "cheesecake")); and it will still work.