The following works with nightly but seems impossible to achieve on stable (the intention is to allow an async function foo that is passed as an argument to an async function baz to take a reference generated within the baz function):
type R<'a> = impl Future<Output = String> + 'a;
async fn baz<F>(text: String, f: F) -> String
where
for<'a> F: FnOnce(&'a str) -> R<'a>,
{
let result = f(&*text).await;
result
}
Solving this on stable requires Pin<Box<dyn Future<...> + 'a>> because the lifetime 'a otherwise cannot be used to constrain the opaque return type of the FnOnce. Is there a fundamental reason behind this restriction?
You don't actually need a trait talking about lifetimes directly, or a trait with any methods. The trait can instead just treat the argument whole function argument generically. And the right supertraits allow you to use still use proper closure call syntax and not need any new methods.
Indeed, there are more workarounds as you and @steffahn show. This feels like a confirmation for my suspicion that the desirable function signature is indeed sound, but it is prevented by syntax restrictions, which is why I asked here about it.
In other words, why is there no way to write this signature down with a single where clause?
where for<'a> F: FnOnce(&'a str) -> R, R: Future<Output = String> + 'a
felt natural to me, but the 'a lifetime cannot be used in the definition of R. Another attempt was
which is rejected on the grounds that R + 'a is a trait object — which can be made to work by boxing, of course, but again it is not obvious in terms of language design why the addition of a lifetime to this type constraint is interpreted as a trait object.
Due to my engineering experience I expect that there are reasons behind these choices, and I’d like to know them It feels like there are some aspects of opaque types that cause these wrinkles in the user experience: we need to talk about some parts of a thing that we keep deliberately hidden.
I don't think that there is a good reason for this restriction beyond it's uncommon. So people don't ask about it. So no one's fixing it. This problem only manifests if you are using HRTB, which is already uncommon. Then you are mixing in an associated type that depends on the HRTB lifetime.
However, I think that associated type bounds could be extended to support this, without extra helper traits.
Good to know! I think this is becoming far less uncommon now that people are starting to routinely use async fn — this pops up for any higher-order async function that passes a reference to the inner function.