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}