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);
}
}
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.
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 Floor
s 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.