youchoose icon indicating copy to clipboard operation
youchoose copied to clipboard

Using a FnMut closure with Preview

Open PanopticaRising opened this issue 3 years ago • 2 comments

Hey, Rust newbie here. I ran into a problem using the Preview feature, and couldn't figure out what the correct way to handle it was. I'm probably not thinking about approaching what I want to do correctly, so any feedback is appreciated.

I'm trying to get the Preview to display the output from a rand random generator, which needs mut access.

Here's the code itself. I tried making r a function that would return the closure that I intend to run with the mut rng moved into scope, but that changes the type and prevents it from running.

fn main() {
    let opt = Opts::parse();
    
    let mut rng: Pcg64 = Seeder::from(opt.seed).make_rng();

    let r = move |rng: Pcg64| {
        |dice| -> String {
            let r: u16 = rng.gen_range(1..=get_upper_bound_of_dice(dice));
            r.to_string()
        }
    };

    let mut menu = youchoose::Menu::new(Dice::iter()).preview(r(rng));

    menu.show();
}

I'm looking at the youchoose code now to see if I can make the typing on .preview more permissive, but if I'm doing something conceptually wrong with my approach, I'd love to know. Thanks!

PanopticaRising avatar Aug 09 '21 05:08 PanopticaRising

Adding the error:

error[E0525]: expected a closure that implements the `Fn` trait, but this closure only implements `FnMut`
  --> src/main.rs:66:9
   |
66 |         |dice| -> String {
   |         ^^^^^^^^^^^^^^^^ this closure implements `FnMut`, not `Fn`
67 |             let r: u16 = rng.gen_range(1..=get_upper_bound_of_dice(dice));
   |                          --- closure is `FnMut` because it mutates the variable `rng` here
...
73 |     let mut menu = youchoose::Menu::new(Dice::iter()).preview(r(rng));
   |                                                       ------- the requirement to implement `Fn` derives from here

PanopticaRising avatar Aug 09 '21 05:08 PanopticaRising

I was able to pass my function through by changing the type expected from Fn to FnMut, but I doubt that's a stable solution.

diff --git a/src/lib.rs b/src/lib.rs
index 11dc59d..636d276 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -195,8 +195,8 @@ where
             if let Some(item) = self.iter.next() {
                 let mut new_item =
                     Item::new(&item, self.item_icon, self.chosen_item_icon);
-                if let Some(preview) = &self.preview {
-                    new_item.preview(item, &preview.func);
+                if let Some(preview) = &mut self.preview {
+                    new_item.preview(item, &mut preview.func);
                 }
                 self.state.items.push(new_item);
             } else {
@@ -322,7 +322,7 @@ where
     /// String.
     pub fn preview<F>(mut self, func: F) -> Menu<'a, I, D>
     where
-        F: Fn(D) -> String + 'static,
+        F: FnMut(D) -> String + 'static,
     {
         let func = DispFunc::new(Box::new(func));
         self.screen.set_pos(ScreenSide::Left, 0.5);
@@ -704,7 +704,7 @@ impl<'a> Item<'a> {
         &self.repr
     }
 
-    fn preview<D: fmt::Display>(&mut self, thing: D, func: &DispFunc<D>) {
+    fn preview<D: fmt::Display>(&mut self, thing: D, func: &mut DispFunc<D>) {
         self.preview = Some(func.eval(thing));
     }
 }
@@ -817,17 +817,17 @@ struct DispFunc<D>
 where
     D: fmt::Display,
 {
-    func: Box<dyn Fn(D) -> String>,
+    func: Box<dyn FnMut(D) -> String>,
 }
 
 impl<D> DispFunc<D>
 where
     D: fmt::Display,
 {
-    fn new(func: Box<dyn Fn(D) -> String>) -> DispFunc<D> {
+    fn new(func: Box<dyn FnMut(D) -> String>) -> DispFunc<D> {
         DispFunc { func }
     }
-    fn eval(&self, param: D) -> String {
+    fn eval(&mut self, param: D) -> String {
         (*self.func)(param)
     }
 }

PanopticaRising avatar Aug 10 '21 05:08 PanopticaRising