use crate::{Error, EvalType, Result, TypeInfo};
use std::{any::Any, ops::Deref};
pub struct DynFn {
pub arg_type: TypeInfo,
pub ret_type: TypeInfo,
boxed_fun: Box<dyn ClonableAny>,
}
impl DynFn {
pub fn new<Arg, Ret>(
f: impl for<'a> Fn(&'a Arg) -> Ret::RefType<'a> + Clone + 'static,
) -> Self
where
Arg: EvalType,
Ret: EvalType,
{
Self {
boxed_fun: Box::new(BoxedFn(Box::new(f))),
arg_type: Arg::type_info(),
ret_type: Ret::type_info(),
}
}
pub fn downcast<Arg, Ret>(&self) -> Result<BoxedFn<Arg, Ret>>
where
Arg: EvalType,
Ret: EvalType,
{
Ok(self.boxed_fun.as_any().downcast_ref().cloned().ok_or_else(
|| Error::InternalDynFnDowncastError {
expected_arg: Arg::type_info(),
expected_ret: Ret::type_info(),
got_arg: self.arg_type,
got_ret: self.ret_type,
},
)?)
}
}
impl Clone for DynFn {
fn clone(&self) -> Self {
DynFn {
boxed_fun: self.boxed_fun.clone_box(),
..*self
}
}
}
pub struct BoxedFn<Arg, Ret>(Box<dyn ClonableFn<Arg, Ret>>)
where
Ret: EvalType;
impl<Arg, Ret> Clone for BoxedFn<Arg, Ret>
where
Ret: EvalType,
{
fn clone(&self) -> Self {
self.clone_boxed()
}
}
impl<Arg, Ret> Deref for BoxedFn<Arg, Ret>
where
Ret: EvalType,
{
type Target = Box<dyn ClonableFn<Arg, Ret>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
trait ClonableAny: Any {
fn clone_box(&self) -> Box<dyn ClonableAny>;
fn as_any(&self) -> &dyn Any;
}
impl<T: Any + Clone> ClonableAny for T {
fn clone_box(&self) -> Box<dyn ClonableAny> {
Box::new(self.clone())
}
fn as_any(&self) -> &dyn Any {
self
}
}
pub trait ClonableFn<Arg, Ret>:
for<'a> Fn(&'a Arg) -> Ret::RefType<'a>
where
Ret: EvalType,
{
fn clone_boxed(&self) -> BoxedFn<Arg, Ret>;
}
impl<Arg, Ret, F> ClonableFn<Arg, Ret> for F
where
Ret: EvalType,
F: for<'a> Fn(&'a Arg) -> Ret::RefType<'a> + Clone + 'static,
{
fn clone_boxed(&self) -> BoxedFn<Arg, Ret> {
BoxedFn(Box::new(self.clone()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dyn_fn() {
let dyn_fn = DynFn::new::<_, i64>(|a: &(i64, i64)| a.0);
let concrete_fn = dyn_fn.downcast::<(i64, i64), i64>().unwrap();
assert_eq!((concrete_fn)(&(10, 20)), 10);
}
}