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