3

There are already a lot of threads on this topic but I fail to see if the discussed problems apply to my specific problem.

I have a structure that stores a name and a callback function. Stripped down to the problem it looks like this:

pub struct Command<'a> {
    name: &'a str,
    callback: &'a Fn(&[&str]) -> ()
}

impl <'a> Command<'a> {
    pub fn new(name: &'a str, callback: &'a Fn(&[&str]) -> ()) -> Command<'a> {
        Command {
            name: name,
            callback: callback
        }
    }
}

What I want to do is store a callback function associated with a name (and prob. more stuff in the future).

But when I try to use this bit of code like so:

fn main() {
    let play_callback = |args| {
        println!("Playing something.");
        for arg in args {
            println!("{}", arg);
        }
    };
    let play_command = Command::new("play", &play_callback);
}

I get the following error message:

src/main.rs:22:42: 22:56 error: type mismatch resolving `for<'r, 'r> <[closure@src/main.rs:16:22: 21:3] as std::ops::FnOnce<(&'r [&'r str],)>>::Output == ()`:
 expected bound lifetime parameter ,
    found concrete lifetime [E0271]
src/main.rs:22  let play_command = Command::new("play", &play_callback);
                                                        ^~~~~~~~~~~~~~

I tried to inline the closure like this

fn main() {
    let play_command = Command::new("play", &|args| {
        println!("Playing something.");
        for arg in args {
            println!("{}", arg);
        }
    });
}

but then I get another error

src/main.rs:16:47: 21:7 error: borrowed value does not live long enough

which I believe I understand why I get.

I tried using a generic type parameter for Command before switching to a function reference first to store in my Command structure, but when I wanted to initialize a HashSet of command objects like this:

let mut commands: HashSet<Command> = HashSet::new();

the compiler wanted me to specify the generic parameter which I think I cannot do as that would mean I could only store the same closure in all my Command objects.

So my question would be: How can I achieve what I want and what is the best way to do so (and why)?

Torsten Scholz
  • 856
  • 1
  • 9
  • 22

2 Answers2

6

The easy fix (by ljedrz) is that args: &[&str] is not inferred in this case. However, it may not be enough to solve your problem.

When you use a reference to some trait as a function argument, it is treated as a trait object. In this case, the &Fn is a trait object that references the closure on the stack.

An easy analogy of trait objects are objects that implement interfaces in other languages.

However, lifetimes work a bit differently with the trait objects. You can think of them as "detached" from the usual ownership flow. If we were to annotate the Fn trait object lifetime 'c in your example, we would get the following code:

pub struct Command<'a> {
    name: &'a str,
    callback: &'a for<'c> Fn(&'c [&'c str]) -> ()
}

impl <'a> Command<'a> {
    pub fn new<'r>(name: &'r str, callback: &'r for<'c> Fn(&'c [&'c str]) -> ()) -> Command<'r> {
        Command {
            name: name,
            callback: callback
        }
    }
}

fn main() {
    let play_callback = |args: &[&str]| {
        println!("Playing something.");
        for arg in args {
            println!("{}", arg);
        }
    };
    let play_command = Command::new("play", &play_callback);
}

In the code above, the lifetime 'c describes a scope in which the callback function will be called.

The above code, however, is not very practical. It couples the command to the scope in which the closure was created (remember, the trait object references the closure in that scope). So you can't exit from the function in which your Command was created, because that would destroy the closure!

The likely solution is to store the closure on the heap, in Box<Fn(&[&str])>. The lifetime of a trait object in the box (heap memory) is controlled by the creation and destruction of the box, so it is the broadest possible ('static).

pub struct Command<'a> {
    name: &'a str,
    callback: Box<Fn(&[&str]) -> ()>
}

impl <'a> Command<'a> {
    pub fn new<'r>(name: &'r str, callback: Box<Fn(&[&str]) -> ()>) -> Command<'r> {
        Command {
            name: name,
            callback: callback
        }
    }
}

fn main() {
    let play_callback = |args: &[&str]| {
        println!("Playing something.");
        for arg in args {
            println!("{}", arg);
        }
    };
    let play_command = Command::new("play", Box::new(play_callback));
}

In the example above, the closure will be moved into the box when the box is created, and will be destroyed together with Command.

Nercury
  • 344
  • 1
  • 9
2

Have you tried specifying the type of args? The following compiles for me:

fn main() {
    let play_callback = |args: &[&str]| {
        println!("Playing something.");
        for arg in args {
            println!("{}", arg);
        }
    };
    let play_command = Command::new("play", &play_callback);
}

I don't know why it is not inferred, though.

ljedrz
  • 20,316
  • 4
  • 69
  • 97