I want to write a generic function named exec that can handle similar callback function. But I ran into a lifetime error. Rewriting exec into a macro_rules! fixes the problem, but I still prefer generic. How to fix exec so that it works?
Code that I own:
use std::fmt::Display;
use foreign_crate::ForeignType;
fn main() {
dbg!(exec(ForeignType::foo));
dbg!(exec(ForeignType::bar));
}
fn exec<Callback, Value>(callback: Callback) -> Option<String>
where
Callback: FnOnce(&ForeignType) -> Option<Value>,
Value: Display,
{
let param = ForeignType;
callback(¶m).map(|x| x.to_string())
}
Part of the puzzle is that callback types need higher-ranked for<'a> lifetime, so that you can have lifetime of the callback argument be limited to just the function call rather tried to some outer large scope.
But the second problem here is that Value doesn't take a lifetime and is declared outside of Callback: for<'a> scope, so there's no syntactic way to explain to the borrow checker that it's limited by the ad-hoc lifetime 'a.
Sadly, Callback: for<'a> FnOnce(&'a ForeignType) -> Option<impl Display + 'a> is not supported.
...correspond to a single monomorphized type (and lifetime-carrying structs with different lifetimes are different types). Here, Rust doesn't let you elide the return type (on stable [1]) or use impl Display in this position or do anything else to sneak in a generic, lifetime-dependent type underneath the higher-ranked binder directly. The helper trait CallOnce let's you introduce such a type (MaybeBorrowed) with bounds for a single lifetime, and then you can wrap a higher-ranked condition around that (CallAny, not strictly needed but more ergonomic).
This approach gets you further, but you still may hit other inference or normalization issues.
on unstable, you can often write a trait for this type of scenario, because the unboxed_closures features lets you write the bounds without mentioning the output type correctly; however, sometimes you hit issue 90950, and for this particular case, it doesn't work because you need a bound on Value and not the output of the function, Option<Value>↩︎
Damn, that's complicated! Assuming I want to redo that same thing but with different trait bounds (replacing Display with a different trait), does it mean I have to create another pair of CallOnce and CallAny?
I did find a way to push the bound into the higher-ranked trait, sort of. Basically, the trait method on the implementation can take advantage of the high-ranked bound, where for whatever reason the stand-alone function with the same bounds cannot. Sorry for the random comments in the code, they're mostly for myself in case I want to add more to the issue on GitHub.
There's so much boilerplate involved, I don't know that you really save anything though. I'd probably want a macro for either approach if I needed many of these.