Returning boxed closures and lifetime issues


#1

I’m trying to implement system which may «talk» to user: ask questions and print messages. When you ‘exec’ plugin, it returns enum which specified which action must be performed by system and contains callback which must be called with result of this action.

Here is working implementation of this idea in Scala:

import scala.util.control.Breaks._

sealed abstract class Action;
case class Print(text: String) extends Action
case class Ask(text: String, cb: (String) => Action) extends Action

trait Plugin {
    def exec(): Action
}

class PluginImpl extends Plugin {
    def exec(): Action = {
        Ask("are you sure?", this.react)
    }
    
    private def react(answer: String): Action = {
        answer match {
            case "yes" => Print("confirmed")
            case "no"  => Print("rejected")
            case _ => ???
        }
    }
}

val plugin = new PluginImpl
var action = plugin.exec()
breakable { while (true) {
    action match {
        case Print(text) =>
            println(text);
            break;
        case Ask(prompt, cb) =>
            println(prompt);
            action = cb("yes");
    }
}}

However, I don’t know how to even express lifetimes with same code in Rust. It is assumed that callbacks are valid as long as Plugin instance lives.

http://is.gd/1JT8vN

enum Action {
  Print(&'static str),
  Ask(&'static str, Box<Fn(&str) -> Action>),
}

trait Plugin {
  fn exec(&self) -> Action;
}

struct PluginImpl;

impl PluginImpl {
  pub fn new() -> Self { PluginImpl }
  
  fn react(&self, answer: &str) -> Action {
    match answer {
      "yes" => Action::Print("confirmed"),
      "no" => Action::Print("rejected"),
      _ => unreachable!(),
    }
  }
}

impl Plugin for PluginImpl {
  fn exec(&self) -> Action {
    Action::Ask("Are you sure?", Box::new(|answer| self.react(answer)));
  }
}

fn main() {
  let plugin: Box<Plugin> = Box::new(PluginImpl::new());
  let mut action = plugin.exec();
  loop {
    match action {
      Action::Print(text) => {
        println!("{}", text);
        break;
      },
      Action::Ask(text, cb) => {
        println!("{}", text);
        let mut input = String::new();
        std::io::stdin().read_line(&mut input).unwrap();
        action = cb(&input);
      },
    }
  }
}

errors:

<anon>:26:43: 26:70 error: cannot infer an appropriate lifetime for capture of `self` by closure due to conflicting requirements [E0495]
<anon>:26     Action::Ask("Are you sure?", Box::new(|answer| self.react(answer)));
                                                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~
<anon>:26:43: 26:70 note: first, the lifetime cannot outlive the expression at 26:42...
<anon>:26     Action::Ask("Are you sure?", Box::new(|answer| self.react(answer)));
                                                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~
<anon>:26:43: 26:70 note: ...so type `[closure@<anon>:26:43: 26:70 self:&&PluginImpl]` of expression is valid during the expression
<anon>:26     Action::Ask("Are you sure?", Box::new(|answer| self.react(answer)));
                                                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~
<anon>:26:34: 26:42 note: but, the lifetime must be valid for the expression at 26:33...
<anon>:26     Action::Ask("Are you sure?", Box::new(|answer| self.react(answer)));
                                           ^~~~~~~~
<anon>:26:34: 26:42 note: ...so that a type/lifetime parameter is in scope here
<anon>:26     Action::Ask("Are you sure?", Box::new(|answer| self.react(answer)));
                                           ^~~~~~~~
<anon>:26:34: 26:71 error: the type `[closure@<anon>:26:43: 26:70 self:&&PluginImpl]` does not fulfill the required lifetime [E0477]
<anon>:26     Action::Ask("Are you sure?", Box::new(|answer| self.react(answer)));
                                           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
note: type must outlive the static lifetime
error: aborting due to 2 previous errors
playpen: application terminated with error code 101
Compilation failed.

#2

Well, there are two ways this can go. First, you can add the necessary lifetime annotations to convince the compiler that the plugin will outlive the closure:

enum Action<'a> {
  Print(&'static str),
  Ask(&'static str, Box<(Fn(&str) -> Action<'a>) + 'a>),
}

trait Plugin {
  fn exec(&self) -> Action;
}

struct PluginImpl;

impl PluginImpl {
  pub fn new() -> Self { PluginImpl }
  
  fn react<'a>(&'a self, answer: &str) -> Action<'a> {
    match answer {
      "yes" => Action::Print("confirmed"),
      "no" => Action::Print("rejected"),
      _ => unreachable!(),
    }
  }
}

impl Plugin for PluginImpl {
  fn exec(&self) -> Action {
    Action::Ask("Are you sure?", Box::new(move |answer| self.react(answer)))
  }
}

fn main() {
  let plugin: Box<Plugin> = Box::new(PluginImpl::new());
  let mut action = plugin.exec();
  loop {
    match action {
      Action::Print(text) => {
        println!("{}", text);
        break;
      },
      Action::Ask(text, cb) => {
        println!("{}", text);
        let mut input = String::new();
        std::io::stdin().read_line(&mut input).unwrap();
        action = cb(&input);
      },
    }
  }
}

The other is to use an Rc to share ownership of the plugin between the code that created the plugin and the closure:

use std::rc::Rc;

enum Action {
  Print(&'static str),
  Ask(&'static str, Box<Fn(&str) -> Action>),
}

trait Plugin {
  fn exec(&self) -> Action;
}

struct PluginImpl;

impl PluginImpl {
  pub fn new() -> Self { PluginImpl }
  
  fn react(&self, answer: &str) -> Action {
    match answer {
      "yes" => Action::Print("confirmed"),
      "no" => Action::Print("rejected"),
      _ => unreachable!(),
    }
  }
}

impl Plugin for Rc<PluginImpl> {
  fn exec(&self) -> Action {
    let self_ = (*self).clone();
    Action::Ask("Are you sure?", Box::new(move |answer| self_.react(answer)))
  }
}

fn main() {
  let plugin: Box<Plugin> = Box::new(Rc::new(PluginImpl::new()));
  let mut action = plugin.exec();
  loop {
    match action {
      Action::Print(text) => {
        println!("{}", text);
        break;
      },
      Action::Ask(text, cb) => {
        println!("{}", text);
        let mut input = String::new();
        std::io::stdin().read_line(&mut input).unwrap();
        action = cb(&input);
      },
    }
  }
}

#3

Thank you! These were the missing bits:

Box<(Fn(&str) -> Action<'a>) +'a>
move |answer| self.react(answer)