Change:
I tried this code: I expected to see this happen: the code is correct. Instead, this happened: Just swap the functions, then the code compiles: Update : The examples may be misleading. I forgot that takes effect on a single item. It was a typo. Here is a new example. It should be correct. But the compiler does not accept it. <!-- If you're using the stable version of the compiler, you should also check if the bug also exists in the beta or nightly versions. --: :
Very strange. I'm thinking we have some stateful HIR lowering code that is hanging onto state it shouldn't.
This bug existed as far back as November 2019 (when was stabilized), so it's not a recent regression.
Marking this as P-medium based on the
I did some digging into this. I'm brand new to the compiler so slowly learning the architecture along the way. Below are some thoughts on what I've seen so far. The desugared HIR for both samples is the same: async first async last As expected both resolves the same set of lifetimes (from ) and process them in the same order (since ). Dump from the property at the first iteration: async first async last Interestingly both included the lifetime as used only once in the opaque/desugared async function and both . However, for the second case (async last) the compilation still succeeds - this is really confusing and I'm probably missing something big about the compiler architecture. Need to dig deeper.
The examples may be misleading. I forgot that takes effect on a single item. It was a typo. Here is a new example.
Makes sense. Then the behavior is no longer magical and makes a lot of sense.
Based on what I've seen the desugaring process is working correctly and the object is keeping the correct state. The lifetime linting identifies the lifetime as only used once within the return opaque type generated by the desugaring process. This behavior seems correct since we'd still want to catch this for an opaque type implementation from the user. My suggestion would be to include an exception in the linting specifically for opaque types of async functions that use a lifetime name already declared in the generic params of the parent function. I have validated this simple change (I named the loop ) and propose to include it alongside .
One more question for the team ( what would we expect in this case? This compiles successfully, which is counter intuitive by looking at the function signature. It compiles because the desugared generator uses the same lifetime as the async function.

Discussion:
// ensure that we issue lints in a repeatable order def_ids.sort_by_cached_key(|&def_id| self.tcx.def_path_hash(def_id)); <del> for def_id in def_ids { </del> <ins> 'lifetimes: for def_id in def_ids { </ins> debug!("check_uses_for_lifetimes_defined_by_scope: def_id = {:?}", def_id); let lifetimeuseset = self.lifetime_uses.remove(&def_id);

{ continue; } <ins> // opaque types generated when desugaring an async function can have a single // use lifetime even if it is explicitly denied (Issue #77175) if let hir::Node::Item(hir::Item { kind: hir::ItemKind::OpaqueTy(ref opaque), .. }) = self.tcx.hir().get(parent_hir_id) { if opaque.origin != hir::OpaqueTyOrigin::AsyncFn { continue 'lifetimes; } // We want to do this only if the liftime identifier is already defined // in the async function that generated this. Otherwise it could be // an opaque type defined by the developer and we still want this // lint to fail compilation for p in opaque.generics.params { if defined_by.contains_key(&p.name) { continue 'lifetimes; } } } </ins> } }

<ins> #[deny(single_use_lifetimes)] // edition:2018 // check-pass // Prior to the fix, the compiler complained that the 'a lifetime was only used // once. This was obviously wrong since the lifetime is used twice: For the s3 // parameter and the return type. The issue was caused by the compiler // desugaring the async function into a generator that uses only a single // lifetime, which then the validator complained about becauase of the // single_use_lifetimes constraints. async fn bar<'a>(s1: String, s2: &'_ str, s3: &'a str) -> &'a str { s3 } fn foo<'a>(s1: String, s2: &'_ str, s3: &'a str) -> &'a str { s3 } fn main() {} </ins>