static_on_stack/
lib.rs

1//! Safely wrap the promotion of a short-lived reference to a `'static` reference, under the
2//! condition that it is passed to a function that never terminates.
3//!
4//! See [`promote_to_static()`] for both how to use it and why it is assumed to be sound.
5#![no_std]
6
7//! Execute the function `f`, and pass it `&T` promoted to be a `&'static T`.
8//!
9//! ## How this is sound
10//!
11//! The precondition for this to be sound is that the function not only never terminates, but that
12//! any panic flying out of the function causes an immediate abort of the program. A drop guard is
13//! in place around the function's execution that makes any panic a double panic.
14//!
15//! As [per the Drop documentation](https://doc.rust-lang.org/nightly/core/ops/trait.Drop.html) a
16//! double panic "will likely abort the program" (i.e. it is not guaranteed), an extra panic guard
17//! is in place that runs an infinite loop on drop. That is not very pretty, but it will only even
18//! make it into optimized code if there is any way in which the double panic does *not* cause an
19//! abort, in which case it does serve its critical role of ensuring that the lifetime of the
20//! original argument still does not end.
21pub fn promote_to_static<T: 'static>(local: &T, f: impl FnOnce(&'static T) -> Never) -> ! {
22    struct DropGuard;
23    impl Drop for DropGuard {
24        fn drop(&mut self) {
25            panic!("Must not unwind out of a function that promotes statics");
26        }
27    }
28    struct WorstCaseDropGuard;
29    impl Drop for WorstCaseDropGuard {
30        fn drop(&mut self) {
31            // It'd be tempting to call out to an extern function that is known not to exist, but
32            // without optimizations enabled, this drop will stay in the code even though it is
33            // only executed after an abort.
34            loop {}
35        }
36    }
37    let _worst_case_drop_guard = WorstCaseDropGuard;
38    let _guard = DropGuard;
39    // unsafe: See module level documentation
40    f(unsafe { core::mem::transmute(local) })
41}
42
43
44#[doc(hidden)]
45pub trait ReturnTypeExtractor {
46    type ReturnType;
47}
48impl<T> ReturnTypeExtractor for fn() -> T {
49    type ReturnType = T;
50}
51/// An alias for the `!` type, accessed through trait trickery originally described [by
52/// SkiFire13](https://github.com/rust-lang/rust/issues/43301#issuecomment-912390203). It is
53/// present only to allow expressing a `FnOnce(...) -> !` in the argument requirement of
54/// [`promote_to_static`].
55pub type Never = <fn() -> ! as ReturnTypeExtractor>::ReturnType;
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60
61    #[test]
62    // This would need a #[should_abort to be actually useful :-/
63    fn it_works() {
64        let a = 42;
65        promote_to_static(&a, |&a| panic!("Don't know what to do with {a}"));
66    }
67}