Repository navigation
Description
Minimal reproducer (edition 2024, where return-position impl Trait captures all lifetimes):
trait Id { type Out: ?Sized; }
impl<X: ?Sized> Id for X { type Out = X; }
fn f<T: std::fmt::Display>(t: <&T as Id>::Out) -> impl std::fmt::Display {
t
}
fn main() {
let mut x = 0;
let a = (|| f(&x))();
x = 1; // should be rejected: `a` borrows `x`
println!("{a}");
}This compiles on current nightly and has undefined behaviour under Miri (an aliasing violation). It is rejected by NLL (-Zpolonius=off: E0506), by 1.100.0-beta.5 and by 1.99.0 stable (edition 2024). Writing the parameter as &T, or calling f(&x) directly, are both correctly rejected.
The same happens with an async fn, whose return type is an opaque impl Future. This is how it was first found:
use std::future::Future;
use std::pin::pin;
use std::task::{Context, Poll, Waker};
trait Id { type Out: ?Sized; }
impl<X: ?Sized> Id for X { type Out = X; }
async fn multiple_named_lifetimes<'a, 'b>(a: <&'a u8 as Id>::Out, _: &u8) -> &'a u8 {
a
}
fn main() {
let mut a = 22u8;
let b = 44u8;
let future = (|| multiple_named_lifetimes(&a, &b))();
a += 1; // should be rejected: `future` still borrows `a`
let mut fut = pin!(future);
let mut cx = Context::from_waker(Waker::noop());
if let Poll::Ready(r) = fut.as_mut().poll(&mut cx) {
println!("{}", *r);
}
}Expected: error[E0506]: cannot assign to a because it is borrowed, as with NLL.
Actual: compiles. Under Miri:
error: Undefined Behavior: trying to retag from <505> for SharedReadOnly permission at alloc206[0x0], but that tag does not exist in the borrow stack for this location
--> src/main.rs:8:43
|
8 | async fn multiple_named_lifetimes<'a, 'b>(a: <&'a u8 as Id>::Out, _: &u8) -> &'a u8 {
| ^ this error occurs as part of retag at alloc206[0x0..0x1]
Both ingredients are required. Each of these is correctly rejected:
- the same program with
a: &'a u8(no projection); - the direct call
multiple_named_lifetimes(&a, &b)(no closure).
Versions / bisection:
| Configuration | Result |
|---|---|
nightly-2026-10-13 (e9bbadf68), default |
accepted |
nightly-2026-10-13, -Zpolonius=off / -Zpolonius=legacy |
rejected |
nightly-2026-10-13, -Zpolonius=next |
accepted |
nightly-2026-10-13, -Znext-solver=coherence and =globally |
accepted with both trait solvers |
-Zpolonius=next on nightly-2025-01-01 |
rejected |
-Zpolonius=next on nightly-2025-07-01 and later |
accepted |
default nightly, 2026-08-05 (1ed2df61a19) |
rejected |
default nightly, 2026-08-06 (7608eb7b07e, the merge of #159343 "Enable polonius alpha on nightly") |
accepted |
| 1.100.0-beta.5, 1.99.0 stable | rejected |
So the bug has been present in Polonius alpha since the first half of 2025. #159343 made it reachable on default nightly.
Possibly related: #160979 ("A possible regression with -Zpolonius=next", A-impl-trait) and #161038 ("Unsoundness in Polonius involving impl Trait"). The async fn return type is an opaque impl Future, so this may share their root cause. The trigger here (a projection-typed parameter plus a closure call) is different.
LLM disclosure
The bug was found by an automated testing tool developed with the assistance of an LLM (Claude). This report text was drafted by an LLM and reviewed/edited by me.
Meta
rustc --version --verbose:
rustc 1.101.0-nightly (e9bbadf68 2026-10-12)
commit-hash: e9bbadf6824e65b3583c2dda3a0926ec76df9143
Activity
impl Traitvalue through a closure drops the borrow it captures #164546