In the following example, I'd like to call test without the need to cast the closure explicitely:
pub trait AsFnPtr {}
impl AsFnPtr for fn() {}
fn test(x: impl AsFnPtr) {}
fn caller() {
test((|| {}) as fn()); // Works
//test(|| {}); // the trait bound `{closure@src/registrar.rs:22:10: 22:12}: AsFnPtr` is not satisfied. The trait `AsFnPtr` is implemented for fn pointer `fn()`
}
It works, if I implement it like impl<TFn: Fn()> AsFnPtr for TFn {}, but this is not what I need, as the implementation of AsFnPtr for fn() depends on beign a fn-pointer.
I tried to use the following signature, but this doesn't work either:
fn test(x: impl Into<fn()>) {}
I also tried to constrain it with a artificial associated type.
pub trait AsFnPtr {
type Foo;
}
impl<TFn: Fn()> AsFnPtr for TFn {
type Foo = Self;
}
fn test(x: impl AsFnPtr<Foo = fn()>) {}
fn caller() {
test((|| {}) as fn()); // Works
//test(|| {}); // type mismatch resolving `<{closure@registrar.rs:22:10} as AsFnPtr>::Foo == fn()`
}
I'd need a trait like "CoercesTo", but I didn't find anything like this...
From your example alone I can't see a reason for the AsFnPtr marker trait. Passing fn() as argument directly to test would work just fine for closures that can be coerced to a fn() function pointer. Would you mind expanding a bit on why AsFnPtr is necessary in your real world use-case?
In my real use case, I'd like to overload this function with fn(), fn(TDependency), fn(TDependency, TDependency2)... for my crate minfac, similar to how Axum is doing it. Eventually, I'd like to use it like this:
let mut collection = ServiceCollection::new();
collection.register(|| 42i32);
collection.register(|Registered(x): Registered<i32>| x as i64);
let provider = collection.build().unwrap();
assert_eq!(Some(42), provider.get<i64>());
Eventually, all structures of this crate should have a stable ABI (repr(C)), so it can be used in unrelated builds and accross multiple rust compiler versions. Therefore, I cannot use rusts build in TraitObjects. Everything works except of this nasty casting I can't get rid of...
Could you use something like the double-boxing trick that lets you use arbitrary closures as FFI callbacks?
For example (not tested):
#[repr(C)]
struct Thunk {
call: fn(*mut ()),
free: fn(*mut ()),
data: *mut ()
}
impl<F:FnMut()+'static> From<F> for Thunk {
fn from(f:F)->Self {
let data = Box::into_raw(Box::new(f)) as *mut _;
let call = (|data: *mut ()| {
// Safety: will only ever be called with a single pointer,
// which is really of type F
unsafe {
(*(data as *mut F))()
}
}) as fn(*mut ());
let free = (|data: *mut ()| {
// Safety: will only ever be called with a single pointer,
// which is really of type F
unsafe {
let _ = Box::<F>::from_raw(data as *mut F);
}
}) as fn(*mut ());
Thunk { call, free, data }
}
}
impl Thunk {
pub fn call(&mut self) {
(self.call)(self.data)
}
}
impl Drop for Thunk {
fn drop(&mut self) {
(self.free)(self.data)
}
}
PS. I just realized that this particular implementation will probably run into issues if you try to extend it for overloading the argument list-- The same type could theoretically implement both FnMut() and FnMut(Something), which would lead to an ambiguity.
True, I cannot use a generic implementation for overloading... Maybe this is the current dead end for this kind of TypeSystem Tricks. I'll probably just add an additional method, as the dependencies can already be overloaded as tuples:
let mut collection = ServiceCollection::new();
collection.register(|| 42i16);
collection.register_with(|Registered(x): Registered<i16>| x as i32);
collection.register_with(
|(Registered(x), Registered(y)): (Registered<i16>, Registered<i32>)| x as i64 + y as i64,
);
let provider = collection.build().unwrap();
assert_eq!(Some(84), provider.get::<i64>());
Sometimes, named fn's are registered and it was combersome, to jump back and forth, if the named fn received a additional argument from DI... But this solves this inconvenience as well...