Change:
initializer_span: Span, else_block: &Block, visibility_scope: Option<SourceScope>, <ins> remainder_scope: region::Scope, </ins> remainder_span: Span, pattern: &Pat<'tcx>, ) -> BlockAnd<()> { <del> let scrutinee = unpack!(block = self.lower_scrutinee(block, init, initializer_span)); let pat = Pat { ty: init.ty, span: else_block.span, kind: Box::new(PatKind::Wild) }; let mut wildcard = Candidate::new(scrutinee.clone(), &pat, false); self.declare_bindings( visibility_scope, remainder_span, pattern, ArmHasGuard(false), Some((None, initializer_span)), ); let mut candidate = Candidate::new(scrutinee.clone(), pattern, false); let fake_borrow_temps = self.lower_match_tree( block, initializer_span, pattern.span, false, &mut [&mut candidate, &mut wildcard], ); // This block is for the matching case let matching = self.bind_pattern( self.source_info(pattern.span), candidate, None, &fake_borrow_temps, initializer_span, None, None, None, ); // This block is for the failure case let failure = self.bind_pattern( self.source_info(else_block.span), wildcard, None, &fake_borrow_temps, initializer_span, None, None, None, ); </del> <ins> let (matching, failure) = self.in_if_then_scope(remainder_scope, |this| { let scrutinee = unpack!(block = this.lower_scrutinee(block, init, initializer_span)); let pat = Pat { ty: init.ty, span: else_block.span, kind: Box::new(PatKind::Wild) }; let mut wildcard = Candidate::new(scrutinee.clone(), &pat, false); this.declare_bindings( visibility_scope, remainder_span, pattern, ArmHasGuard(false), Some((None, initializer_span)), ); let mut candidate = Candidate::new(scrutinee.clone(), pattern, false); let fake_borrow_temps = this.lower_match_tree( block, initializer_span, pattern.span, false, &mut [&mut candidate, &mut wildcard], ); // This block is for the matching case let matching = this.bind_pattern( this.source_info(pattern.span), candidate, None, &fake_borrow_temps, initializer_span, None, None, None, ); // This block is for the failure case let failure = this.bind_pattern( this.source_info(else_block.span), wildcard, None, &fake_borrow_temps, initializer_span, None, None, None, ); this.break_for_else(failure, remainder_scope, this.source_info(initializer_span)); matching.unit() }); </ins> // This place is not really used because this destination place // should never be used to take values at the end of the failure // block.

Discussion:
results in I am not sure if this issue exists only in combination with impl Trait of if it can be triggered in some other ways as well, haven't managed to get this bug some other way. cc do you know whether this behavior is known already?
The bug is not known to me. cc who has improved the borrow checking behavior. The suggestion is also weird.
The let-else actually desugars to this if-let: And you . However the suggestion actually makes sense for if-let, because putting a semicolon afterwards does indeed fix it. The suggestion is wrong for the let-else because let-else's desugaring is always in tail position in its enclosing block, any statements thereafter get moved into the if-branch and so no amount of semicolons can take the desugaring out of tail position.
Also note that the old desugaring, which was , does not suffer this particular problem because the temporary has a name and lives outside the tail position expression. That way there is no ordering problem with the ends of x's and the unnamed temporary's lifetimes. So a few things: a named temporary like that is a good suggestion to make instead for let-else, and potentially for if-let as well. presence or absence of a semicolon on a tail position expression of type () having that much significance is pretty bad. Arguably it should not be significant whatsoever. is being a bit obtuse. It isn't explained very well even for the if-let version why the unnamed temporary is still alive when x is being dropped. Conceptually I think borrowck is trying to say that the real implicit return expression is actually that empty space inside the braces of type (). I don't know. if it did explain better, is there a technical reason why borrowck can't figure out that it could simply drop the unnamed temporary first? Because that would be ideal.
Could we just wrap the in the let-else desugaring with a ?
Well isn't that convenient! Yeah, I suppose we could.
I've just tried this, and it also suffers the same thing did, with the unreachable code warnings when the desugaring ends up with a return in both branches. It is probably complaining about the DropTemps expression itself being unreachable.

We can't slap an on it lest it ignore all unreachable code inside, but I think we can disable the emission of such warnings on any DropTemps in typeck's . Right? (Edit, maybe instead in , because this DropTemps will rarely if ever be such that typeck could warn about it.) Example warning:
Ah cool, turned out to be pretty easy.
This compiles successfully as of , which includes . So I'd say it's fixed, but it needs a test before the issue can be closed. includes a test for this, but it's not merged and probably not going to be. The other test in that PR also looks interesting.