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}