TL;DR:
Assume we have following trait and a function that uses it:
trait SingleMethodTrait<T> {
fn do_stuff<'a>(self, &'a T) -> impl Future<Output = ()> + use<'a>;
}
async fn do_bigger_stuff(
do_stuff: impl SingleMethodTrait<u32>,
) {
...
let inner_param = 42u32;
do_stuff.do_stuff(&inner_param).await;
...
}
Then, we should be able to provide that trait's implementation by writing closure:
do_bigger_stuff(async |i| {
println!("{i}");
}).await;
This should work if trait has exactly one non-static method (with any variation of self parameter), all its associated types are referenced in that method's signature (i.e. can be inferred from usage spot), and it's the only non-marker trait in the bound declared.
Rationale
Allow convenient syntax for implementation of any Fn-like trait, with all variations of method signature available. This should also allow higher-kinded functions, as nothing forbids us from methods like fn show_it(&self, item: &impl Debug).
Also removes need to make more stdlib traits for functions.
Prior art
- Java Functional Interfaces, Oracle Java Tutorial - Lambda Expressions
- Initial attempt from 2017 Closure syntax for non-function traits
Additional possible extensions
- Syntax or macro to declare such functional trait in-place:
fn do_stuff( func: callable! { fn call<'a>(self, &'a mut Foo) -> impl Future<Output = ()> + use<'a>; } - Marker trait
Callablewhich tells compiler that this trait is equivalent to any other marked trait with the same signature of its method
Details
Recently I hit issue with declaring proper async closure type yet again. It was caused by lifetime bounds propagation from arguments to returned future, but that's not what I'd like to talk about.
What I observe is that we're multiplying number of magic traits each time we hit some new limitation. We started with Fn* traits to cover passing functions - yet they couldn't resolve linking closure's lifetime to its output. We added AsyncFn* when we decided to resolve this issue for async functions, for which it was really critical. We're talking about LendingFn* traits family which should land inbetween those two. And all these traits are magical and have special meaning to compiler.
Yet, certain nontrivial cases with callables' signatures are still not covered. Some may be never covered, and all this is because you can't express whole range of possible method signatures with parametrizing traits, at least it seems so now. Rustaceans often workaround this kind of issue with their custom traits, where all the bounds needed are written explicitly. Unfortunately, providing implementation for such traits is often tedious, since you need to write down all the captured variables and their types, esp. if that closure is used only once.
What I'd like to discuss is the ability to generate trait implementation from a closure for any compatible trait.