Как мне создать вектор замыканий, который может принимать любой аргумент? - PullRequest
0 голосов
/ 05 августа 2020

Это мой текущий код:

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

Я хочу иметь возможность делать что-то вроде этого, чтобы каждое из замыканий могло принимать аргумент произвольного типа:

Arc<dyn Fn(T: any)>

Например:

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

Ответы [ 3 ]

1 голос
/ 05 августа 2020

Вы можете до некоторой степени имитировать динамический набор c, используя черту Any. Эта черта реализуется большинством типов, и ее объекты типа Any могут быть понижены до конкретного типа.

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)));
}

( Площадка )

При вызове одно из замыканий, вам нужно сначала поместить аргумент:

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

Передача неправильного типа приведет к пани c, поскольку мы использовали unwrap() в результате метода downcast() вызов. В зависимости от ваших потребностей, вы можете вернуть ошибку вызывающей стороне или обработать ее другим способом. Вам нужно будет запомнить, какой индекс в векторе соответствует типу аргумента, чтобы иметь возможность фактически вызывать эти функции. Я не знаю вашего варианта использования, но почти наверняка есть лучшие решения, использующие настраиваемую черту и динамическую c отправку.

1 голос
/ 05 августа 2020

Я не думаю, что это возможно сделать напрямую, но если ваш тип T может быть сериализован, то вот способ. Пусть listeners будет Vec<Arc<dyn Fn(String)>>, и при вставке в этот Ve c передайте лямбда, которая преобразует String в T и вызывает фактическую функцию прослушивателя. Вот рабочий код того, что я имею в виду:

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");
}

Идею можно еще немного уточнить: возможно, некоторые Arc не понадобятся, и, возможно, есть лучший «базовый» тип, чем String (возможно, это можно сделать для работы с любым типом, который работает с serde), вы можете использовать &str вместо String как есть.

Поскольку вы спрашивали об использовании serde, вот пример:

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 });
}

Обратите внимание, что bincode::deserialize вернет Ok(_), если байты могут быть преобразованы в заданную запрошенную структуру, и, следовательно, почему вы видите странные значения выше (может быть способ добавить тег типа, чтобы 'неправильные' структуры не десериализуются).

0 голосов
/ 06 августа 2020

Самый надежный способ, который я придумал, заключался в использовании необработанных указателей и небольшого небезопасного кода.

Оберните закрытие внутри другого закрытия: вызовите обратный вызов, вы сначала преобразуете значение в необработанный указатель:

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());
    }
}

Затем тип слушателя станет:

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

Это позволяет нам иметь массив, который принимает любое количество функций которые имеют разные типы в качестве параметров.

Другие опубликованные решения не работали для структур или других расширенных типов - работали только примитивные типы. Кажется, это решение работает для всего.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...