【问题标题】:How do I create a vector of closures that can take in any argument?如何创建一个可以接受任何参数的闭包向量?
【发布时间】:2020-08-05 11:48:39
【问题描述】:

这是我当前的代码:

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

【问题讨论】:

  • 如果不做一些不安全的类型魔法,我认为这是不可能的。想一想:如果你能做到这一点,编译器将如何检查任何给定的调用是否为闭包提供了正确的参数类型?必须有一些共同的特征可以用作所有闭包的共同点。
  • 只是好奇 - 你为什么要这样做?
  • 假设你有一个函数,上面写着fn foo(a: any)(正确写成fn foo&lt;T&gt;(a: T))——你打算用a做什么?你基本上什么都做不了,因为你不知道这个类型有什么能力。
  • @Emoun 我看过使用不安全的 transmute 将 args 转换为字节向量。然后将该字节向量转换为泛型类型。但是,这不是一个可行的解决方案,因为某些计算机可能是小端/大端,并且内存偏移量可能是错误的等。如果您可以在闭包本身上应用泛型类型会很好 - 例如:Fn(T)但似乎这在 rust 中是不可能的。

标签: generics vector rust


【解决方案1】:

我不认为直接这样做是可能的,但是如果你的类型 T 可以序列化,那么这里有一个方法。让listeners 成为Vec&lt;Arc&lt;dyn Fn(String)&gt;&gt;,并在插入此 Vec 时,传递一个从 String 转换为 T 的 lambda 并调用实际的侦听器函数。这是我的意思的工作代码:

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

这个想法可以再细化一点:也许不需要一些 Arcs,也许有比 String 更好的“基本”类型(可能这可以用于任何与 serde 一起使用的类型),您可以使用 &amp;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(_),因此您会在上面看到奇怪的值(可能有一种方法可以添加类型标记以便“错误”结构不要反序列化)。

【讨论】:

  • 我给了你一个赞成但没有被接受的答案,因为这很有帮助,但对于我正在寻找的东西来说还不够好。我尝试使用 serde 而不是字符串进行反序列化,但是 serde 似乎真的不喜欢将字节反序列化为泛型类型,所以我无法让它工作;它适用于字符串和其他基本类型,但是当您尝试使用结构时它会失败。
  • @DylanKerler,我添加了一个使用 serde 的示例。我认为您可能错过了 DeserializeOwned 特征,这就是它对您不起作用的原因。我很想知道您为什么需要这样做。
【解决方案2】:

您可以使用Any trait 在某种程度上模拟动态类型。大多数类型都实现了这个 trait,Any 类型的 trait 对象可以向下转换为具体类型。

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)

调用其中一个闭包时,需要先将参数装箱:

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

传递错误的类型会导致恐慌,因为我们在downcast() 方法调用的结果上使用了unwrap()。根据您的需要,您可以将错误返回给调用者或以不同的方式处理。

这仍然是一个悬而未决的问题,您将如何实际使用这个。您必须记住向量中的哪个索引对应于能够实际调用这些函数的参数类型。我不知道你的用例,但几乎可以肯定有更好的解决方案使用自定义特征和动态调度。

【讨论】:

    【解决方案3】:

    我想出的最稳健的方法是使用原始指针和一些不安全的代码。

    将闭包包裹在另一个闭包中:

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

    然后每次我们要调用回调时,您首先将值转换为原始指针:

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

    那么 listeners 类型变为:

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

    这允许我们拥有一个数组,它可以接收任意数量的具有不同类型作为参数的函数。

    发布的其他解决方案不适用于结构或其他高级类型 - 只有原始类型有效。这个解决方案似乎适用于一切。

    【讨论】:

    • 这允许我们拥有一个数组,该数组可以接收任意数量的具有不同类型的函数作为参数 - 真的,真的 t,因为除非您有正确类型的参数,并且如果您知道正确的类型先验,否则您无法安全地调用这些函数,您可以改用Fn(T)。这样做没有任何合乎逻辑的理由。
    • 是的。你误会了。它允许我们拥有 vec![Fn(i32), Fn(u32]。请原谅伪代码。
    • @DylanKerler 这整个方法对我来说似乎天生就是错误的。你将如何处理数组中的函数?您必须记住如何实际调用每个单独的函数,并且每个函数都需要不同类型的参数。你从哪里得到这些论点?如果您有一个可以传递给函数的固定类型列表,那么枚举不是一个选项吗?我完全不明白你打算如何使用它。 (顺便说一句,同样的反对意见也适用于我自己的答案。这个答案要安全得多,因为如果你弄错了它只会恐慌,而你的代码会导致 UB。)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-28
    • 1970-01-01
    • 1970-01-01
    • 2012-03-10
    • 1970-01-01
    • 2014-06-18
    相关资源
    最近更新 更多