-1

This is my current code:

pub struct EventEmitter {
    pub listeners: Vec<Arc<dyn Fn()>>,
}

I want to be able to do something like this so that each of the closures can take in an argument of an arbitrary type:

Arc<dyn Fn(T: any)>

For example:

let first = |foo: i32| { // i32 type parameter
    foo + 1;
}; 
let second = |bar: String| println!("{}", bar); // String type parameter

let emitter = EventEmitter {
    listeners: Vec::new(),
};
emitter.listeners.push(Arc::new(first)); // This currently fails
emitter.listeners.push(Arc::new(second)); // This currently fails
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Dylan Kerler
  • 2,007
  • 1
  • 8
  • 23
  • 5
    I don't think this is possible without doing some unsafe type magic. Think of this: If you could do this, how would the compiler check that any given call do a closure is providing it the correct argument types? There must be some common trait could be used as a common ground for all the closures. – Emoun Aug 05 '20 at 12:17
  • 1
    Just curious - why do you want to do this? – nullptr Aug 05 '20 at 12:31
  • 4
    Let's say you have a function that says `fn foo(a: any)` (properly written as `fn foo(a: T)`) — what are you going to *do* with `a`? You basically can't do **anything** because you don't know what capabilities the type has. – Shepmaster Aug 05 '20 at 13:32
  • @Emoun I have had a look at using transmute with unsafe to convert the args to a byte vector. And then cast that byte vector to the generic type. However this is not a feasable solution as some computers may be little endian/big endian and offset of memory might be wrong etc. It would be good if you could apply generic types on a closure itself - e.g.: Fn(T) But it seems this is not possible in rust. – Dylan Kerler Aug 05 '20 at 16:04

3 Answers3

1

I don't think it is possible to do this directly, but if your type T can be serialized, then here is a way. Have listeners be a Vec<Arc<dyn Fn(String)>>, and when inserting to this Vec, pass a lambda that converts from String to T and calls the actual listener function. Here is a working code of what I mean:

use std::sync::Arc;
struct EventEmitter {
    listeners: Vec<Arc<dyn Fn(String)>>,
}

impl EventEmitter {
    pub fn add_listener<T>(&mut self, listener: Arc<dyn Fn(T)>)
    where
        T: std::str::FromStr, // So that `s` can be converted to type `t`.
        <T as std::str::FromStr>::Err: std::fmt::Debug, // So that `e` can be printed.
        T: 'static, // See https://stackoverflow.com/a/29740792/8111265
    {
        self.listeners.push(Arc::new(move |s| {
            match s.parse::<T>() {
                Ok(t) => listener(t),
                Err(e) => println!("Oops! we couldn't convert {:?} to type T due to {:?}", s, e),
            };
        }));
    }

    pub fn notify(&self, s: &str) {
        for listener in self.listeners.iter() {
            listener(s.to_string());
        }
    }
}

#[test]
fn test_event_emitter() {
    let mut e = EventEmitter { listeners: vec![] };

    // i32 implements `std::str::FromStr`.
    e.add_listener(Arc::new(|x: i32| {
        println!("Got {:?} in i32 listener", x);
    }));

    // std::net::IpAddr implements `std::str::FromStr`.
    e.add_listener(Arc::new(|ip_addr: std::net::IpAddr| {
        println!("Got {:?} in IpAddr listener", ip_addr);
    }));

    // This line prints:
    // Got 42 in i32 listener
    // Oops! we couldn't convert "42" to type T due to AddrParseError(())
    e.notify("42");

    // This line prints:
    // Oops! we couldn't convert "127.0.0.1" to type T due to ParseIntError { kind: InvalidDigit }
    // Got V4(127.0.0.1) in IpAddr listener
    e.notify("127.0.0.1");
}

The idea can be refined a bit more: perhaps some Arcs will not be needed, and maybe there is a better 'base' type than String (possibly this can be made to work with any type that works with serde), you can use &str instead of String as it is.

Since you asked about using serde, here's an example:

use serde::{Deserialize, Serialize};
use std::sync::Arc;

#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct PointInts {
    x: i32,
    y: i32,
}

#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct PointFloats {
    x: f32,
    y: f32,
}

struct EventEmitter {
    listeners: Vec<Arc<dyn Fn(&[u8])>>,
}

impl EventEmitter {
    pub fn add_listener<T>(&mut self, listener: Arc<dyn Fn(T)>)
    where
        T: serde::de::DeserializeOwned,
        T: 'static, // See https://stackoverflow.com/a/29740792/8111265
    {
        self.listeners.push(Arc::new(move |bytes| {
            match bincode::deserialize(bytes) {
                Ok(t) => listener(t),
                Err(e) => println!(
                    "Oops! we couldn't convert the bytes {:?} to type T due to {:?}",
                    bytes, e
                ),
            };
        }));
    }

    pub fn notify<T>(&self, obj: T)
    where
        T: serde::Serialize,
    {
        let bytes = bincode::serialize(&obj).unwrap();
        for listener in self.listeners.iter() {
            listener(&bytes);
        }
    }
}

#[test]
fn test_event_emitter() {
    let mut e = EventEmitter { listeners: vec![] };

    // PoinitInts implements Serialize and Deserialize.
    e.add_listener(Arc::new(|p: PointInts| {
        println!("Got {:?} in PointInts listener", p);
    }));

    // PointFloats implements Serialize and Deserialize.
    e.add_listener(Arc::new(|p: PointFloats| {
        println!("Got {:?} in PointFloats listener", p);
    }));

    // This line prints:
    // Got PointInts { x: 42, y: 999 } in PointInts listener
    // Got PointFloats { x: 0.000000000000000000000000000000000000000000059, y: 0.0000000000000000000000000000000000000000014 } in PointFloats listener
    e.notify(PointInts { x: 42, y: 999 });

    // This line prints:
    // Got PointInts { x: 1109917696, y: 1120327434 } in PointInts listener
    // Got PointFloats { x: 42.0, y: 99.42 } in PointFloats listener
    e.notify(PointFloats { x: 42.0, y: 99.420 });
}

Note that bincode::deserialize will return Ok(_) if the bytes can be converted to the given requested struct, and hence why you see weird values above (there may be a way to add a type tag so that 'wrong' structs don't get deserialized).

nullptr
  • 505
  • 3
  • 10
  • 1
    I gave you an upvote but not the accepted answer since this was helpful but isn't really good enough for what i was looking for. I tried deserializing with serde instead of into a string however serde seems to really not like deserializing from bytes into a generic type so I couldn't get that to work; It works with strings and other basic types but when you try to use a struct it fails. – Dylan Kerler Aug 05 '20 at 16:11
  • @DylanKerler, I've added an example that uses serde. I think you might have missed the DeserializeOwned trait, which is why it didn't work for you. I'm curious to know why you need to do this. – nullptr Aug 05 '20 at 17:59
1

You can emulate dynamic typing to some degree using the Any trait. This trait is implemented by most types, and trait objects of type Any can be downcasted to a concrete type.

use std::any::Any;
use std::sync::Arc;

pub struct EventEmitter {
    pub listeners: Vec<Arc<dyn Fn(Box<dyn Any>)>>,
}

fn wrap<F, T>(f: F) -> impl Fn(Box<dyn Any>)
where
    F: Fn(T),
    T: Any,
{
    move |x| f(*x.downcast::<T>().unwrap())
}

fn main() {
    let first = |foo: i32| {
        let _ = foo + 1;
    };
    let second = |bar: String| println!("{}", bar);

    let mut emitter = EventEmitter {
        listeners: Vec::new(),
    };
    emitter.listeners.push(Arc::new(wrap(first)));
    emitter.listeners.push(Arc::new(wrap(second)));
}

(Playground)

When calling one of the closures, you need to box the argument first:

emitter.listeners[1](Box::new("Hello world!".to_owned()))

Passing a wrong type will result in a panic, since we used unwrap() on the result of the downcast() method call. Depending on your needs, you can return the error to the caller or handle it in a different way.

This still leaves the open question how you would actually use this. You will have to remember which index in the vector corresponds to what argument type to be able to actually call these functions. I don't know your use case, but there are almost certainly better solutions using a custom trait and dynamic dispatch.

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
-2

The most robust way I came up with was using raw pointers and a little unsafe code.

Wrap the closure inside of another closure:

let callback = |value: &str| {};

let wrapped_callback = move |raw_pointer: usize| {
    unsafe {
       let value: T = &*(raw_pointer as *const T);
       callback(value);
   }
}

then each time we want to call the callback you convert the value to a raw pointer first:

pub fn<T> notify(&self, value: T) {
    let raw_pointer = &value as *const T as usize;
    for callback is self.listeners.iter() {
        wrapped_callback(raw_pointer.clone());
    }
}

Then listeners type becomes:

pub listeners: Vec<Arc<dyn Fn(usize)>>,

This allows us to have an array that takes in any number of functions that have different types as their parameters.

The other solutions posted did not work for structs or other advanced type - only primitive types worked. This solution seems to work for everything.

Dylan Kerler
  • 2,007
  • 1
  • 8
  • 23
  • *This allows us to have an array that takes in any number of functions that have different types as their parameters* - It really, **really** doesn't, because you can't safely call those functions unless you have an argument of the correct type, and if you know the correct type *a priori*, you could have used `Fn(T)` instead. There is no logical reason to ever do this. – trent Aug 06 '20 at 02:24
  • Yes it does. You misunderstand. It allows us to have vec![Fn(i32), Fn(u32]. Forgive the pseudo code. – Dylan Kerler Aug 06 '20 at 07:05
  • 2
    @DylanKerler This whole approach seems inherently wrong to me. What will you do with the functions in the array? You will have to remember how to actually call each individual function, and each will need a different type of argument. Where do you get these arguments from? If you have a fixed list of types you could pass to the functions, wouldn't an enum be an option? I completely don't understand how you plan to use this. (The same objections apply to my own answer, by the way. That answer is a lot safer, as it will simply panic if you get it wrong, whereas your code will result in UB.) – Sven Marnach Aug 06 '20 at 08:22