General function as argument

Is it possible to have a trait that gets a general function (for example, a function with two arguments and a return value, where both arguments and the return value are references), logs that the function was called, and runs the function, returning it's output.

I.e., to be able to do, something like, for example,
f.log_and_run(&arg1, &arg2)

Or maybe
log_and_run(f)(&arg1, &arg2)

(Dealing with general arity is not that hard since you can use markers. The hard part is that I don't know how to specify "all functions regardless of lifetime relationships")

P.s., gpt said that it's impossible since "there is no support for higher kinded types". Regardless, I wanted to ask to double-check, and also to learn whether there are workarounds (in particular, I tried to think of a way to do that even at the cost of exponential blow-up of markers, (since it wont be that bad for functions with few arguments) but I got stuck since it's not clear to me how to specify all the possible lifetime bounds of the return argument in a way where all the definitions never overlap).

Thanks in advance

1 Like

For workarounds, I think you can write a procedural macro crate, and use syn to process the function and generate the logging code. This is the neat way in your particular use case, IMO.

There's no "generic type constructors" to allow something like

//        vvvvvvvvvvvv  Made up language feature
fn foo<F, T<..>, U<..>>(f: F)
where
    F: FnOnce(T<'_>) -> U<'_>

That would support both fn(&str) -> Chars<'_> and fn(&RefCell<String>) -> RefMut<'_, String> in a way that supports all input/output lifetimes, say.

But, do you really need that? If the arguments are truly unconstrained, you presumably aren't calling them on local variables -- you're calling them on values the caller also gave you. In which case you probably don't need the type constructors -- the argument types are known to the caller, including lifetimes, and so the return value can be too. I.e. you probably don't need to support all input/output lifetimes.

In other words, do you have an example where this doesn't work for you, aside from arity?

fn run<Ret, Args, F: FnOnce(Args) -> Ret>(f: F, args: Args) -> Ret {
    // log and
    f(args)
}
2 Likes

Both solutions work. Thanks for the help!

1 Like