How to pass a function to a function [solved]

I'm having a problem trying to pass a function to a function:

use anyhow::{bail, Result};
use crate::event:
pub type OnEventFn = fn(&Event) -> Result<()>;
pub struct Uxf {
    custom: String,
    comment: String,
    value: Value,
...
    on_event: OnEventFn,
}
#[derive(Clone, Debug)]
pub struct Event {
    pub kind: EventKind, // an enum
    pub code: i16,
    pub message: String,
...
}
impl Uxf {
    pub fn new(custom: &str, comment: &str, on_event: Option<OnEventFn>) -> Self {
        Uxf {
            custom: custom.to_string(),
            comment: comment.to_string(),
            value: Value::List(List::default()),
...
            on_event: if let Some(on_event) = on_event {
                on_event // user's
            } else {
                event::on_event // default
            },
        }
    }
...
// And separately in a test file:
#[cfg(test)]
mod tests {
    use anyhow::Result;
    use uxf::event::Event;
    use uxf::Uxf;

    #[test]
    fn t_uxf_strings() {
        let mut events = Vec::<Event>::new();
        fn on_error(event: &Event) -> Result<()> {
            events.push(event.clone());
            Ok(())
        }
        events.clear();
        let mut uxo = Uxf::new("custom 1", "comment 1", Some(on_error));
...

The idea is instead of bail!()ing when an event occurs (as would be the case using event::on_event), the custom on_event simply records the event. Then at the end of the tests I want to compare the events I got with the events I expected to get to see that I did/did not get the expected events.
But I can't get this to complile:

warning: `uxf` (lib) generated 1 warning
warning: `uxf` (lib test) generated 1 warning (1 duplicate)
   Compiling uxf v0.4.0 (/home/mark/app/uxf/rs)
error[E0434]: can't capture dynamic environment in a fn item
  --> tests/test_uxf.rs:14:13
   |
14 |             events.push(event.clone());
   |             ^^^^^^
   |
   = help: use the `|| { ... }` closure form instead

I did try using a closure, i.e., Some(|event| { ... }) but it didn't help.

Functions are literally just a pointer to some machine code, so it's not valid for your fn on_error() to reference events from an external scope - if you can only pass around a pointer to some machine code, where do we store the reference to events?

Closures are the right tool to use here because they get converted into a struct which implements the Fn() trait.

Are you able to share the error message you ran into?

There's a good chance the code complained because your OnEventFn is a bare function pointer, and a closure has its own type. The way to handle this is to "hide" the closure's type by putting it behind a Box.

Here is an example of what I mean:

type OnEventFn = Box<dyn Fn(&Event)>;

struct Uxf { on_event: OnEventFn }

impl Uxf {
    fn new(on_event: Option<OnEventFn>) -> Self {
        Uxf {
            on_event: on_event.unwrap_or_else(|| Box::new(default_handler)),
        }
    }

    fn trigger_event(&self, event: Event) {
        (self.on_event)(&event);
    }
}

struct Event {}

fn default_handler(_: &Event) {
    println!("Received an event");
}

You can also make it more ergonomic by using a constructor that accepts a closure directly. By having one constructor that uses the default handler and a separate constructor that uses a custom version, you can make the caller's code a lot cleaner.

impl Uxf {
    fn with_event_handler(on_event: impl Fn(&Event) + 'static) -> Self {
        Uxf {
            on_event: Box::new(on_event),
        }
    }
}

impl Default for Uxf {
    fn default() -> Self {
        Uxf::with_event_handler(default_handler)
    }
}

Using those APIs, the end user would write this:

fn main() {
    // use the default handler
    let uxf = Uxf::new(None);
    uxf.trigger_event(Event {});

    // use a custom handler
    let uxf = Uxf::new(Some(Box::new(|_| {
        println!("Custom handler!");
    })));
    uxf.trigger_event(Event {});

    // A slightly more ergonomic constructor
    let uxf = Uxf::with_event_handler(|_| println!("Custom handler 2!"));
    uxf.trigger_event(Event {});
    
    // another way to get the default constructor
    let uxf = Uxf::default();
    uxf.trigger_event(Event {});
}

(playground)

5 Likes

Thanks for the explanation. Your code works great for the normal use case.
However, I also want to be able to keep a list of events (e.g., for testing), but trying to do so doesn't work:
playground
All I changed was:

#[derive(Clone, Debug)] // added
struct Event {}
...
    // added
    let mut events = Vec::<Event>::new();
    let uxf = Uxf::with_event_handler(|event| {events.push(event.clone())});
    println!("{:?}", &events);

Which gave me:

error[E0597]: `events` does not live long enough
  --> tests/test_uxf.rs:17:17
   |
16 |               Some(Box::new(|event| {
   |                    -        ------- value captured here
   |  __________________|
   | |
17 | |                 events.push(event.clone());
   | |                 ^^^^^^ borrowed value does not live long enough
18 | |                 Ok(())
19 | |             })),
   | |______________- cast requires that `events` is borrowed for `'static`
...
44 |       }
   |       - `events` dropped here while still borrowed

error[E0502]: cannot borrow `events` as immutable because it is also borrowed as mutable
  --> tests/test_uxf.rs:42:18
   |
16 |               Some(Box::new(|event| {
   |                    -        ------- mutable borrow occurs here
   |  __________________|
   | |
17 | |                 events.push(event.clone());
   | |                 ------ first borrow occurs due to use of `events` in closure
18 | |                 Ok(())
19 | |             })),
   | |______________- cast requires that `events` is borrowed for `'static`
...
42 |           assert!(&events.is_empty());
   |                    ^^^^^^^^^^^^^^^^^ immutable borrow occurs here

And in fact this is now a problem for me for a normal use case. I have:

pub type Visitor = dyn Fn(&Value);
...
    pub fn visit(&self, visitor: &Visitor) {...}
// But fails on use:
        value.visit(&|v: &Value| {
            if v.is_table() {
                let tclass = v.as_table().unwrap().tclass().clone();
                let ttype = tclass.ttype().to_string();
                self.tclass_for_ttype.insert(ttype, tclass); // WONT COMPILE
            }
        });

So I'm stuck!

The problem is: the event handler merely borrows (mutably) the events vector. Since you go ahead and stow away the event handlers, what should happen when events goes out of scope, but the event handlers are still around?

You could use a move closure (i.e., move |event| { … }) instead, so that it consumes the vector, but then external code couldn't access it, which is I assume what you want. So what you are trying to do here doesn't really make sense as-is.

  1. I solved the problem I had with visit by doing things a completely different way.
  2. In the case of events I just want to gather up a vec of them that I can then check later (e.g., it should be empty in some cases and in other cases should have a sequence of specific events). I don't make any use of the event once it has gone to the handler so it could be passed by value I guess.

I finally realised that I needed interior mutability to solve this.

        let events = Rc::new(RefCell::new(Vec::<Event>::new()));
        let mut uxo = Uxf::new(
            "",
            "",
            Some(Box::new({
                let events = Rc::clone(&events);
                move |event| {
                    let mut events = events.borrow_mut();
                    events.push(event.clone());
                    Ok(())
                }
            })),
        );

With this in place whenever a Uxf method is called that itself calls on_event() the event is pushed onto the events vec and I can then check this at any time.

If you don't need the events vector outside the handler, then you don't need RefCell, either, and you can just move the vector into the handler.

I explained myself poorly.
In the normal case i want a custom on_event() handler called and this will normally either bail! or log or use eprintln().
But when testing the error handling what I prefer to do is have the on_event() handler simply push all events onto an external vec of events so that after I've completed all the actions I can then compare this vec with the events I expected.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.