An async function with one static reference compiles:
pub async fn test_0(_a: &'static str) {
}
An async function with a non-static and a static reference compiles:
pub async fn test_1<'a>(_a: &'a str, _b: &'static str) {
}
An async function with three non-static references compiles:
pub async fn test_2<'a, 'b, 'c>(_a: &'a str, _b: &'b str, _c: &'c str) {
}
A function that takes two non-static references and a static reference and returns a future compiles:
pub fn test_3_desugared<'a, 'b>(_a: &'a str, _b: &'b str, _c: &'static str) -> impl std::future::Future<Output=()> {
std::future::ready(())
}
So why does an async function that takes two non-static references and a static reference not compile?
pub async fn test_3<'a, 'b>(_a: &'a str, _b: &'b str, _c: &'static str) {
}
The compilation error in question:
error[E0700]: hidden type for `impl Trait` captures lifetime that does not appear in bounds
--> src/lib.rs:11:74
|
11 | pub async fn test_3<'a, 'b>(_a: &'a str, _b: &'b str, _c: &'static str) {
| ^
|
note: hidden type `impl Future` captures lifetime smaller than the function body
--> src/lib.rs:11:74
|
11 | pub async fn test_3<'a, 'b>(_a: &'a str, _b: &'b str, _c: &'static str) {
|
Playground link for reference: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=4d90427b4a592ccc3b3d119a326cc401
My current rust version:
$ rustc --version
rustc 1.56.1 (59eed8a2a 2021-11-01)
I came across this issue while trying to add a ring digest algorithm argument to a function that had two reference arguments already. A workaround is pretty easy, as I can wrap the static reference in a struct, pass that in, and use it without issue. I am just curious why this breaks.
pub fn test_3<'a, 'b>(_a: &'a str, _b: &'b str, _c: &'static str) -> impl Future
{
async {}
}
pub async fn test_3<'a, 'b>(_a: &'a str, _b: &'b str, _c: &'static str) {
}
The reason why the impl Future
version compiles is because _a
, _b
, and _c
references are not captured by the future. The compiler is smart enough to remove the references, we can see that with the HIR of the function.
pub fn test_3<'a, 'b>(_a: &'a str, _b: &'b str, _c: &'static str)
-> /*impl Trait*/ { #[lang = "from_generator"](|mut _task_context| { }) }
You can compare it with the HIR of the async version. In this case, the references are all captured.
pub async fn test_3<'a, 'b>(a: &'a str, b: &'b str, c: &'static str)
-> /*impl Trait*/ where
'a:'b #[lang = "from_generator"](move |mut _task_context|
{
let a = a;
let b = b;
let c = c;
{ let _t = { () }; _t }
})
in the async version, all of the input lifetimes to the function are captured in the future returned by the async function, like so impl Future + 'a + 'b + 'static
. In the end, all these lifetimes are unified by the compiler into one lifetime '0. This unification is done by the member constraint algorithm by "least choice" into a set of lifetimes.
for our specific case, we got '0 min(['a, 'b, 'static]). The problem is that there is no obvious minimum in this set since the compiler doesn't know the relationship between 'a and 'b.
If you give this info to the compiler, the async version compiles
pub async fn test_3<'a, 'b>(a: &'a str, b: &'b str, c: &'static str)
where 'a:'b
{
()
}
Another solution could be to choose 'static in the case of no obvious least choice. It is discussed in this PR#89056