I have tried to simplify my problem now a few types without completely destroying the intention of what I'm trying to do but I'm really struggling to understand if there are any meaningful workarounds or what exactly the core issue is.
To set the stage, imagine I have a type that represents a runtime value:
#[derive(Debug)]
enum Value {
String(String),
Number(i64),
}
And this trait and family of implementations to convert or borrow out of the value:
trait TryConvertValue<'a>: Sized {
fn try_convert_value(value: &'a Value) -> Option<Self>;
}
impl<'a> TryConvertValue<'a> for &'a str {
fn try_convert_value(value: &'a Value) -> Option<Self> {
match value {
Value::String(string) => Some(string),
Value::Number(_) => None,
}
}
}
impl<'a> TryConvertValue<'a> for String {
fn try_convert_value(value: &'a Value) -> Option<Self> {
match value {
Value::String(string) => Some(string.clone()),
Value::Number(number) => Some(number.to_string()),
}
}
}
impl<'a> TryConvertValue<'a> for i64 {
fn try_convert_value(value: &'a Value) -> Option<Self> {
match value {
Value::String(_) => None,
Value::Number(number) => Some(*number),
}
}
}
So far, so good. This sort of abstraction works well, so you can do this just fine:
let foo: &str = TryConvertValue::try_convert_value(&value).unwrap();
let foo: String = TryConvertValue::try_convert_value(&value).unwrap();
However, I absolutely cannot find a way to funnel this abstraction through functions. I wanted to box up a bunch of functions that are exposed to a user where the arguments are abstracted through the TryConvertValue
trait. In this simplified example, imagine only a single argument is supported:
trait CallbackTrait<Arg>: Send + Sync + 'static {
fn invoke(&self, args: Arg) -> Value;
}
impl<Func, Arg> CallbackTrait<Arg> for Func
where
Func: Fn(Arg) -> Value + Send + Sync + 'static,
Arg: for<'a> TryConvertValue<'a>,
{
fn invoke(&self, arg: Arg) -> Value {
(self)(arg)
}
}
struct ArgCallback(Box<dyn Fn(&Value) -> Value + Sync + Send + 'static>);
impl ArgCallback {
pub fn new<F, Arg>(f: F) -> ArgCallback
where
F: CallbackTrait<Arg>,
Arg: for<'a> TryConvertValue<'a>,
{
ArgCallback(Box::new(move |arg| -> Value {
f.invoke(TryConvertValue::try_convert_value(arg).unwrap())
}))
}
pub fn invoke(&self, arg: &Value) -> Value {
(self.0)(arg)
}
}
It works for owned values:
let pow = ArgCallback::new(|a: i64| Value::Number(a * a));
But it does not compile for borrowed values:
let to_upper = ArgCallback::new(|a: &str| Value::String(a.to_uppercase()));
23 | let to_upper = ArgCallback::new(|a: &str| Value::String(a.to_uppercase()));
| ^^^^^^^^^^^^^^^^ implementation of `TryConvertValue` is not general enough
|
= note: `TryConvertValue<'0>` would have to be implemented for the type `&str`, for any lifetime `'0`...
= note: ...but `TryConvertValue<'1>` is actually implemented for the type `&'1 str`, for some specific lifetime `'1`
I presume the issue is related to what was being discussed in this thread, however I'm not sure if there are reasonable workarounds for what I am trying to accomplish.