Skip to content

closures are not required to outlive generic arguments mentioned in their body #153140

Description

@lcnr
mod side_table {
    use std::sync::atomic::{AtomicPtr, Ordering};
    static STORAGE: AtomicPtr<String> = AtomicPtr::new(std::ptr::null_mut());
    pub fn set<'a: 'a>(x: &'a String) {
        // TODO: assert that this is the first time we've called `set` to avoid
        // overwriting the value.
        STORAGE.store((&raw const *x).cast_mut(), Ordering::Release)
    }
    /// # Safety
    ///
    /// This must only be called after `set` has been called for the lifetime `'a`.
    pub unsafe fn get<'a>() -> &'a String {
        unsafe { &*STORAGE.load(Ordering::Acquire) }
    }
}
fn yikes<'a>(x: &'a String) -> impl FnOnce() + use<> {
    side_table::set::<'a>(x);
    || {
        // SAFETY: We've put a reference in the storage which is
        // valid for the whole lifetime `'a`
        let print_me = unsafe { side_table::get::<'a>() };
        println!("{print_me}");
    }
}

fn main() {
    let closure = {
        let temp = String::from("temporary");
        yikes(&temp)
    };

    closure();
}

I believe the safety comment in the closure is correct and this should be sound. It explicitly checks that get is called with the lifetime 'a.

This is the even more general version of #84366 #112905

I discussed closure outives requirements with @BoxyUwU recently and there we considered that longterm, we would like the following rules:

  • closures have to outlive all generic parameters mentioned in their signature
  • for any lifetime/type parameter referenced in the body of the closure, we check that this generic parameter is outlived by at least one element of the signature
    • which implies that the closure can only ever be used while these parameters are still valid

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-closuresArea: Closures (`|…| { … }`)A-lifetimesArea: Lifetimes / regionsA-type-systemArea: Type systemI-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessP-lowLow priorityT-typesRelevant to the types team, which will review and decide on the PR/issue.

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    new solver everywhere

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions