I'm trying to build something that takes a closure of variable arity and allowed argument types and return types.
I have made a lot of progress, but it only works for owned types e.g. i64, String, Vec<_>. But I also want the arguments to be able to be references like &str.
And mostly it works fine except in the case with a reference
// works
takes_any_function(|x: bool| if x { 1 } else { 2 });
// works
takes_any_function(|x: bool, y: String| ());
// does not work
let f = |x: &str| x.to_lowercase();
takes_any_function(f);
// ^^^^^^^^^^^^^^^^^^ implementation of `Function` is not general enough
// this works though?
Box::new(move |args: &[Value]| -> Result<Value> {
let args = FunctionArgs::from_values(args)?;
Ok(FromReturn::from_return(Function::call_func(&f, args)))
});
error: implementation of `Function` is not general enough
--> src/main.rs:16:5
|
16 | takes_any_function(f);
| ^^^^^^^^^^^^^^^^^^ implementation of `Function` is not general enough
Is it possible to work around this? Perhaps with an intermediate trait somewhere?
Compiling playground v0.0.1 (/playground)
error: implementation of `Function` is not general enough
--> src/main.rs:16:5
|
16 | takes_any_function(f);
| ^^^^^^^^^^^^^^^^^^ implementation of `Function` is not general enough
|
= note: `[closure@src/main.rs:15:13: 15:39]` must implement `Function<'0, std::string::String, (&str,)>`, for any lifetime `'0`...
= note: ...but it actually implements `Function<'1, std::string::String, (&str,)>`, for some specific lifetime `'1`
error: could not compile `playground` due to previous error
impl<'a, Func, R, T> Function<'a, R, (T,)> for Func
where
Func: Fn(T) -> R,
R: FromReturn,
T: IntoArg<'a>,
Is similar to here. For fn(&str) -> String aka for<'any> fn(&'any str) to meet this bound, it needs to be coerced into a fn(&'singular_lifetime str) -> String as &'any str is not a type (and T must be a singular type). [1]
You can maybe make it work using something along these lines, but also beware that inference really fell apart, and the size of code exploded, when I tried to adapt that approach to multiple parameters.
From the T: IntoArg<'a> bound, we then get that the 'singular_lifetime must be 'a. ↩︎
Compiling but not-cleaned-up version. You can see that inference has gone out the window. I didn't understand the entire design from the start and this could be cleaned up a bit before trying poke at the inference problems. It's probably pretty much the same situation as in that other thread I linked to though.
Guess I should summarize the changes: I added associated types to IntoArg and FunctionArgs, so that GAT-like indirection could be added for &str. For the higher-typedness, IntoArg is implemented for a placeholder type Str instead of &'each str. [1]FunctionArgs needs the associated type to go from the higher-ranked placeholder to the lifetime-carrying concrete type (from Str to some &'specific str).
Then it was largely an exercise in adjusting the bounds and signatures to use the associated types, and and then annotating everywhere to guide inference.
I originally implemented for the standard str, but it turns out you need this to be sized as a placeholder elsewhere. ↩︎