🚀 New: chi (χ) — an open-source autoresearch harness for fleets of LLM coding agents. Read the announcement.

Rust's T: 'static Bound Is Not Checked on Inferred Locals, and Drop Is Only the Messenger

Rust issue #164250 shows a struct with a T: 'static bound accepting a short-lived reference through an inferred local and a field assignment. I compiled the report's repro on rustc 1.97.0 and found the bound is skipped even with no Drop impl. What the open fix, a well-formedness check on MIR temps, changes, and what it means for unsafe-adjacent code today.

Contents

rust-lang/rust#164250, opened on October 13, 2026, shows safe Rust code that stores a reference to a dead stack variable in a struct whose type parameter is declared T: 'static. The reporter says it reproduces on current nightly and on stable back to 1.85.0. The thread’s commenters report variants that have compiled for much longer: one since 1.18.0 in 2017, another since 1.4.

The report is built around a Drop impl, and most of the discussion follows it. I compiled the repro on rustc 1.97.0 and then took the Drop impl out. The program still compiles. That narrows the bug to something more specific than “drop glue trusts a lifetime.”

The repro, verbatim

This is the reporter’s program:

use std::sync::Mutex;
static KEPT: Mutex<Vec<Box<dyn std::any::Any + Send>>> = Mutex::new(Vec::new());
struct Q<T: 'static + Send>(Option<T>);
impl<T: 'static + Send> Drop for Q<T> {
    fn drop(&mut self) { if let Some(t) = self.0.take() { KEPT.lock().unwrap().push(Box::new(t)); } }
}
fn main() {
    {
        let l = String::from("hello world, this is long enough");
        let mut q = Q(None);
        q.0 = Some(&l);
    }
    let _z = String::from("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
    let k = KEPT.lock().unwrap();
    println!("{}", k[0].downcast_ref::<&'static String>().unwrap());
}

The expected result is a compile error, because &l cannot be 'static. The reporter says the program compiles and prints the X string, and that AddressSanitizer reports stack-use-after-scope. On rustc 1.97.0 (2026-07-07) on Linux, it compiled and printed 32 X characters here. I did not run ASan. The output depends on the freed stack slot being reused by _z, so it is undefined behavior that happened to look tidy, not a stable result.

The Drop impl does the damage in this program: when q drops at the end of the inner block, the impl moves the stored &String into a global as a Box<dyn Any + Send>. The impl is allowed to do that only because it is written for T: 'static. Later, downcast_ref::<&'static String>() succeeds, since the type id of the value really is &String with an erased lifetime.

The same hole with no Drop

Here are the two programs I compared. Both use an inferred type for q.

struct Q<T: 'static>(Option<T>);
fn main() {
    let l = String::from("hi");
    let mut q: Q<&String> = Q(None);
    q.0 = Some(&l);
}

With the annotation on q, rustc 1.97.0 rejects this with E0597, “l does not live long enough”, and says the usage requires l to be borrowed for 'static. That is the behavior you expect from the bound.

struct Q<T: 'static>(Option<T>);
fn main() {
    let l = String::from("hi");
    let mut q = Q(None);
    q.0 = Some(&l);
}

Remove the annotation and this compiles, with only unused-variable warnings. No Drop, no Send, no Mutex. The T: 'static requirement on Q<&'a String> is never enforced for the local q, whose type is inferred from a later assignment into its field.

Nothing unsound happens in that second program, since nothing reads the reference after l dies. The Drop impl in the original is what turns the missing check into memory unsafety. It is an observer of the bug rather than its cause, which matters for scoping: any code that relies on a 'static bound on a struct, with or without a destructor, is relying on a check the compiler skipped in this shape.

What the open fix checks

The pull request linked from the issue, #164277, is titled “require MIR local types to be well-formed.” Per its page, it adds a check in compiler/rustc_borrowck/src/type_check/mod.rs so that for MIR locals of kind Temp, the borrow checker requires the local’s type to be well-formed. “Well-formed” is the property that includes Q<&'a String> satisfying 'a: 'static when Q declares T: 'static.

That reading fits my experiment, though it is an inference from the PR title and diff summary, not something the maintainers stated. The explicit annotation gives the compiler a user-written type that it checks. The inferred one gets a type through unification, and the evidence suggests the well-formedness obligation was not attached to it. A commenter on the issue, maxdexh, guessed the same thing in different words: “I think unification just generally doesn’t check lifetime bounds of the type.” Neither the issue nor the PR confirms a root cause, and the PR is open with a review pending.

Two other facts from the thread are worth knowing:

  • The labels include I-unsound and P-high. A maintainer, lcnr, wrote that the bug would have been P-critical regardless of whether people encountered it in the wild, and that exploiting it needs an intentionally malicious Drop implementation.
  • A reviewer asked whether casts and user type ascriptions should also be checked for well-formedness. The PR page showed no answer when I read it.

There is no discussion of breakage or a crater run on the PR page. Any fix that starts rejecting programs that compile today has to find out whether real crates depend on them, and that data does not exist yet in what I read.

What to do with it

Most Rust programs are not affected in a way you can act on. Exploiting this needs a destructor or other code that trusts the 'static bound and then does something observable with the value, and the maintainers describe it as needing a deliberately hostile impl. Your own Drop types that behave normally are not made unsafe by it.

The cases where it does matter are narrower:

  • Running untrusted Rust source. Online playgrounds, grading systems and plugin hosts that compile user code and treat “safe Rust” as a sandbox boundary should treat this as a known hole until a fixed stable ships. This was already a poor sole boundary, and a P-high unsoundness report is a direct reminder.
  • Your own unsafe code that leans on a 'static bound for a safety argument. If the argument is “callers can only construct Foo<T> when T: 'static, so I can stash it across threads,” the compiler is currently the only thing enforcing the first half, and it missed a shape. Audit those proofs for what they assume the type system guarantees.
  • Reading the fix when it lands. If #164277 or a successor starts rejecting code in your dependency tree, the likely error shape is a lifetime error on an inferred local that previously compiled. That is the fix working, not a regression.

I did not test the variants posted in the thread beyond the reporter’s program, so I am not describing them.

Sources

  • rust-lang/rust#164250, “struct’s lifetime requirements can be bypassed via covariant field assignment”: repro, version claims, labels, maintainer comments. Read October 14, 2026.
  • rust-lang/rust#164277, “require MIR local types to be well-formed”: the open fix. Read October 14, 2026.
  • My own compile of the reporter’s program and the two Drop-free variants above, on rustc 1.97.0 (2d8144b78 2026-07-07), x86-64 Linux.