Skip to content

'static closures/FnDefs/futures with non-'static return type are unsound #84366

Open

Description

@steffahn

Argument-free closures can return non-'static types but be 'static themselves:

fn foo<'a>() {
    let closure = || -> &'a str { "" };
    assert_static(closure);
}

fn assert_static<T: 'static>(_: T) {}

However, associated types of 'static types must be 'static, too. So the fact that the above compiles results in unsoundness, as demonstrated below:

use std::fmt;
trait Trait {
    type Associated;
}

impl<R, F: Fn() -> R> Trait for F {
    type Associated = R;
}

fn static_transfers_to_associated<T: Trait + 'static>(
    _: &T,
    x: T::Associated,
) -> Box<dyn fmt::Display /* + 'static */>
where
    T::Associated: fmt::Display,
{
    Box::new(x) // T::Associated: 'static follows from T: 'static
}

fn make_static_displayable<'a>(s: &'a str) -> Box<dyn fmt::Display> {
    let f = || -> &'a str { "" };
    // problem is: the closure type of `f` is 'static
    static_transfers_to_associated(&f, s)
}

fn main() {
    let d;
    {
        let x = "Hello World".to_string();
        d = make_static_displayable(&x);
    }
    println!("{}", d);
}

(playground)

����IV�

@rustbot modify labels: T-compiler, T-lang, A-closures, A-lifetimes, A-traits, A-typesystem, A-associated-items
and someone please add “I-unsound 💥”.

Found this bug while experimenting with variations of #84305.

Without the dyn keyword, this seems to compile “successfully” since Rust 1.4.0. Godbolt link.

Activity

  1. added
    A-closuresArea: Closures (`|…| { … }`)
    A-lifetimesArea: Lifetimes / regions
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    T-langRelevant to the language team
    A-associated-itemsArea: Associated items (types, constants & functions)
    on Apr 20, 2021
  2. added
    I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness
    on Apr 20, 2021
  3. added
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    on Apr 20, 2021
  4. steffahn commented on Apr 21, 2021

    @steffahn
    MemberAuthor

    This also works with functions

    fn make_static_displayable<'a>(s: &'a str) -> Box<dyn fmt::Display> {
        //let f = || -> &'a str { "" };
        fn f<'a>() -> &'a str { "" }
        // problem is: the type of `f::<'a>` is 'static
        static_transfers_to_associated(&f::<'a>, s)
    }

    but only since Rust 1.20. I don’t know if this would be better put as a separate issue since that’s probably a regression then.

    1.19 gives

    error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
      --> <source>:24:36
       |
    24 |     static_transfers_to_associated(&f::<'a>, s)
       |                                    ^^^^^^^^
       |
    note: first, the lifetime cannot outlive the lifetime 'a as defined on the function body at 20:1...
      --> <source>:20:1
       |
    20 | / fn make_static_displayable<'a>(s: &'a str) -> Box<fmt::Display> {
    21 | |     //let f = || -> &'a str { "" };
    22 | |     fn f<'a>() -> &'a str { "" }
    23 | |     // problem is: the type of `f::<'a>` is 'static
    24 | |     static_transfers_to_associated(&f::<'a>, s)
    25 | | }
       | |_^
    note: ...so that reference does not outlive borrowed content
      --> <source>:24:46
       |
    24 |     static_transfers_to_associated(&f::<'a>, s)
       |                                              ^
       = note: but, the lifetime must be valid for the static lifetime...
    note: ...so that the type `fn() -> &str {make_static_displayable::f::<'a>}` will meet its required lifetime bounds
      --> <source>:24:5
       |
    24 |     static_transfers_to_associated(&f::<'a>, s)
       |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    
    error: aborting due to previous error(s)
    
    Compiler returned: 101

    (compiler explorer)

  5. steffahn commented on Apr 21, 2021

    @steffahn
    MemberAuthor

    The version with functions regressed (started compiling without error) at nightly-2017-06-29.

    > rustc +nightly-2017-06-28 --version
    rustc 1.20.0-nightly (f590a44ce 2017-06-27)
    > rustc +nightly-2017-06-29 --version
    rustc 1.20.0-nightly (69c65d296 2017-06-28)
    

    This includes

    4079e6128f6cff3270ed2b0ebdc62fb6b1f4b5d7 - Auto merge of #42417 - eddyb:separate-fn-sig, r=nikomatsakis
    88c3242ef26956266970d62571a1d7d079b5799a - Auto merge of #42431 - nagisa:core-float-2, r=alexcrichton
    5bc89416388f0a852f6e48542bf2fc021682fcb5 - Auto merge of #42709 - stepancheg:discriminant-hash, r=jseyfried
    6b52a1162eff2469c0da88300577863e5cc0c78f - Auto merge of #42931 - arielb1:statement-visitor, r=eddyb
    c16930762a4a7762b26579f7d44273d3bab4c11e - Auto merge of #42745 - sfackler:1.19-stabilization, r=alexcrichton
    47faf1d51952ecd9d4c8a7325332fba34fbe00bd - Auto merge of #42819 - scottmcm:swap-nonoverlapping, r=sfackler
    69c65d29615c391c958ebf75dd65258ec23e175c - Auto merge of #42850 - estebank:unwanted-return-rotj, r=nikomatsakis
    

    Looking at those, almost certainly #42417 is the relevant PR.

  6. apiraino commented on Apr 21, 2021

    @apiraino
    Contributor

    Assigning priority as discussed as part of the Prioritization Working Group procedure and removing I-prioritize.

    @rustbot label -I-prioritize +P-high

  7. added
    P-highHigh priority
    and removed
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    on Apr 21, 2021
  8. 18 remaining items

  9. added 3 commits that reference this issue on Apr 24, 2023
  10. added a commit that references this issue on Apr 24, 2023
  11. added
    S-bug-has-testStatus: This bug is tracked inside the repo by a `known-bug` test.
    on Apr 25, 2023
  12. changed the title [-]'static closures with non-'static return type are unsound[/-] [+]'static closures/FnDefs/futures with non-'static return type are unsound[/+] on May 1, 2024
  13. theemathas commented on Apr 26, 2025

    @theemathas
    Contributor

    Here's another way to use this unsoundness (or something similar) to implement a generic lifetime extension function.

    trait MyFn {
        type Assoc;
        fn my_call(&self) -> Self::Assoc;
    }
    impl<T, F: Fn() -> T> MyFn for F {
        type Assoc = T;
        fn my_call(&self) -> T {
            self()
        }
    }
    
    trait Trait<'a, T> {
        fn put(&mut self, s: &'a T);
        fn get(&self) -> &'static T
        where
            Self: 'static;
    }
    impl<'a, T> Trait<'a, T> for Option<&'a T> {
        fn put(&mut self, s: &'a T) {
            *self = Some(s);
        }
        fn get(&self) -> &'static T
        where
            Self: 'static,
        {
            self.unwrap()
        }
    }
    
    fn wut<'a, T, F: MyFn + 'static>(f: F) -> impl Trait<'a, T> + 'static
    where
        F::Assoc: Trait<'a, T>,
    {
        f.my_call()
    }
    
    pub fn extend<'a, T>(s: &'a T) -> &'static T {
        let mut impl_trait = wut(|| None::<&'a T>);
        impl_trait.put(s);
        impl_trait.get()
    }
    
    fn main() {
        let s = String::from("abcd");
        let s2 = extend(&s);
        drop(s);
        println!("{}", s2);
    }

    This code started compiling in Rust 1.66.0. It gave a compiler error in Rust 1.65.0. The change was probably due to #95474

  14. lcnr commented on Mar 30, 2026

    @lcnr
    Contributor

    This unsoundness also exists for unconstrained lifetime on impls

    use std::fmt::Debug;
    trait Trait<'a> {}
    impl Trait<'_> for () {}
    struct Thing<T>(T);
    impl<'a, T> Thing<T> where T: Trait<'a> {
        fn weird() -> &'a str {
            ""
        }
    }
    
    trait Func {
        type Output: Debug;
    }
    impl<R: Debug, F: FnOnce() -> R> Func for F {
        type Output = R;
    }
    
    fn foo<F: 'static + Func>(f: F, x: F::Output) -> Box<dyn Debug> {
        Box::new(x)
    }
    
    fn main() {
        let x = {
            let temp = String::from("hi");
            foo(Thing::<()>::weird, temp.as_str())
        };
        println!("{x:?}")
    }
  15. oli-obk commented on May 12, 2026

    @oli-obk
    Contributor

    Fun. I think for FnDefs the soundess issue was accidentally fixed (#70164) and then reverted (#71218) because of #70917

  16. theemathas commented on Aug 22, 2026

    @theemathas
    Contributor

    Another way to exploit this, discovered at #t-compiler > Reporting a number of soundness issues in rustc @ 💬

    #![forbid(unsafe_code)]
    trait Key { type R; }
    impl<F: Fn() -> T, T> Key for F { type R = T; }
    
    fn mk<'a>(_: &'a u8) -> impl Key<R = &'a String> + 'static {
        || -> &'a String { unreachable!() } // never called: only its TYPE is used
    }
    
    struct S<K: Key> { _k: K, r: Option<K::R> }
    
    fn main() {
        let anchor = 0u8;
        let k = mk(&anchor); // k: mk::{opaque#0}<'a>
        let b = Box::new(String::from("hello"));
        let s = S { _k: k, r: Some(&*b) }; // s.r: Option<&'a String>, loan on *b
        drop(b); // accepted: 'a is not live here, so the loan is out of scope
        println!("{}", s.r.unwrap()); // reads the freed String: use-after-free
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-associated-itemsArea: Associated items (types, constants & functions)A-async-awaitArea: Async & AwaitA-closuresArea: Closures (`|…| { … }`)A-lifetimesArea: Lifetimes / regionsA-trait-systemArea: Trait systemA-type-systemArea: Type systemAsyncAwait-TriagedAsync-await issues that have been triaged during a working group meeting.C-bugCategory: This is a bug.I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessP-highHigh priorityS-bug-has-testStatus: This bug is tracked inside the repo by a `known-bug` test.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.T-langRelevant to the language teamT-typesRelevant to the types team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions