Skip to main content

startup/
lib.rs

1#![no_std]
2//! Lightweight (zero dependency, proc_macro free) way to run code before main.
3//!
4//! This crate is the moral equivalent to the `ctor` crate, although the API is
5//! completely different. The main reason for it's existence is:
6//!
7//! - Much faster to compile — no proc macros / syn / quote.
8//!
9//! - More obviously safe (avoids things I find dodgy, like `dtor`, ctors that
10//!   initialize data, uses extern "C" function in function array called from C
11//!   ...)
12//!
13//! - Try to handle untested unix platforms by assuming they support *at least*
14//!   the `.ctors` section. This is in line with what clang does to compile c++
15//!   static constructors.
16//!
17//! # Example
18//!
19//! ```
20//! startup::on_startup! {
21//!     println!("I'm running before main");
22//! }
23//! fn main() {
24//!     println!("I'm inside main");
25//! }
26//! ```
27
28/// Run some code automatically before the execution of `main`.
29///
30/// # Example
31///
32/// ```
33/// startup::on_startup! {
34///     println!("I'm running before main");
35/// }
36/// fn main() {
37///     println!("I'm inside main");
38/// }
39/// ```
40///
41/// This outputs:
42///
43/// ```text
44/// I'm running before main.
45/// I'm inside main.
46/// ```
47///
48/// # Caveats
49///
50/// - If your program is loaded as a dynamic library via dlopen/LoadLibrary,
51///   it's not actually run "before main", but instead "when dlopen is called".
52///   In practice, this doesn't matter for most use cases.
53///
54/// - This is on a best effort basis. There are known `rustc` bugs that will
55///   prevent it from working. There are known platforms that don't support it
56///   (wasm, maybe others). It is very important that your programs safety not
57///   rely on this being called.
58///
59/// - The order two different `on_startup` invocations run in is totally
60///   unspecified. Different platforms do wildly different things here. Do not
61///   rely on one particular order. See also C++'s ["static initialization order
62///   fiasco" (or problem)][static_init]
63///
64/// - Not all of the rust stdlib may be supported before main. It's best not to
65///   call into it unless you're certain it will work.
66///
67/// [static_init]: https://isocpp.org/wiki/faq/ctors#static-init-order
68#[macro_export]
69macro_rules! on_startup {
70    ($($tokens:tt)*) => {
71        const _: () = {
72            // pulled out and scoped to be unable to see the other defs because
73            // of the issues around item-level hygene.
74            extern "C" fn __init_function() {
75                // Note: currently pointless, since even when loaded at runtime
76                // via dlopen, panicing before main makes the stdlib abort.
77                // However, if that ever changes in the future, we want to guard
78                // against unwinding over an `extern "C"` boundary, so we force
79                // a double-panic, which will trigger an abort (rather than have
80                // any UB).
81                let _guard = $crate::_private::PanicOnDrop;
82                // Note: ensure we still forget the guard even if `$tokens` has
83                // an explicit `return` in it somewhere.
84                let _ = (|| -> () { $($tokens)* })();
85                $crate::_private::forget(_guard);
86            }
87            {
88                #[used]
89                #[cfg_attr(
90                    any(target_os = "macos", target_os = "ios", target_os = "tvos"),
91                    link_section = "__DATA,__mod_init_func",
92                )]
93                // These definitely support .init_array
94                #[cfg_attr(
95                    any(
96                        target_os = "linux",
97                        target_os = "android",
98                        target_os = "freebsd",
99                        target_os = "netbsd",
100                    ),
101                    link_section = ".init_array"
102                )]
103                // Assume all other unixs support .ctors
104                #[cfg_attr(all(
105                    any(unix, all(target_os = "windows", target_env = "gnu")),
106                    not(any(
107                        target_os = "macos", target_os = "ios",
108                        target_os = "tvos", target_os = "linux",
109                        target_os = "android", target_os = "freebsd",
110                        target_os = "netbsd",
111                    ))
112                ), link_section = ".ctors")]
113                #[cfg_attr(all(windows, not(target_env = "gnu")), link_section = ".CRT$XCU")]
114                static __CTOR: extern "C" fn() = __init_function;
115            };
116        };
117    };
118}
119
120// Note: not part of the public api.
121#[doc(hidden)]
122pub mod _private {
123    pub use core::mem::forget;
124    pub struct PanicOnDrop;
125    impl Drop for PanicOnDrop {
126        #[cold]
127        #[inline(never)]
128        fn drop(&mut self) {
129            panic!("Triggering abort via double panic (static initializer panicked).")
130        }
131    }
132}
133
134#[cfg(test)]
135mod test {
136    use core::sync::atomic::*;
137    static VAL: AtomicU8 = AtomicU8::new(0);
138    // do a few of them.
139    on_startup! { VAL.fetch_add(1, Ordering::Relaxed); }
140    on_startup! { VAL.fetch_add(2, Ordering::Relaxed); }
141    on_startup! { VAL.fetch_add(4, Ordering::Relaxed); }
142
143    #[test]
144    fn smoke() {
145        assert_eq!(VAL.load(Ordering::Relaxed), 1 + 2 + 4);
146    }
147}