Skip to main content

ctor_lite/
lib.rs

1//! The [`ctor`] crate reimplemented using declarative macros.
2//!
3//! [`ctor`]: https://crates.io/crates/ctor
4//!
5//! In some cases it is necessary to run code at the very start or the very end
6//! of the program. This crate provides a macro that can be used to run code at
7//! the very beginning of program execution, along with some extra features.
8//!
9//! ## Advantages over [`ctor`]
10//!
11//! - Completely dependency free, thanks to relying on declarative macros instead
12//!   of proc macros.
13//! - Supports all of the same use cases as the [`ctor`] crate.
14//! - Supports all of the same platforms as the [`ctor`] crate.
15//! - Fixes a couple of warts in [`ctor`]'s API, such as:
16//!   - `unsafe` is required when it is used, see the "Safety" section below.
17//!   - Global variables are required to be `Sync`.
18//!   - Global variables use `MaybeUninit` instead of `Option`.
19//!   - Functions set up with the `ctor` or `dtor` macros cannot be called in
20//!     other Rust code.
21//!
22//! ## Disadvantages
23//!
24//! - The API has a slightly different form factor that can be inconvenient in
25//!   some cases.
26//! - The MSRV has been raised to 1.36.0.
27//! - Docstrings are not supported.
28//!
29//! ## Functional Usage
30//!
31//! The `ctor` macro can be used to run a function at program startup time.
32//!
33//! ```
34//! use std::sync::atomic::{AtomicUsize, Ordering};
35//!
36//! static INITIALIZED: AtomicUsize = AtomicUsize::new(0);
37//!
38//! ctor_lite::ctor! {
39//!     unsafe fn set_value() {
40//!         INITIALIZED.store(1, Ordering::Relaxed);
41//!     }
42//! }
43//!
44//! assert_eq!(INITIALIZED.load(Ordering::Relaxed), 1);
45//! ```
46//!
47//! Note that this macro is a declarative block rather than an attribute macro.
48//! If you prefer the old way of using the macro you can use the
49//! [`macro-rules-attribute`] crate.
50//!
51//! [`macro-rules-attribute`]: https://crates.io/crates/macro-rules-attribute
52//!
53//! ```
54//! use macro_rules_attribute::apply;
55//! use std::sync::atomic::{AtomicUsize, Ordering};
56//!
57//! static INITIALIZED: AtomicUsize = AtomicUsize::new(0);
58//!
59//! #[apply(ctor_lite::ctor!)]
60//! unsafe fn set_value() {
61//!     INITIALIZED.store(1, Ordering::Relaxed);
62//! }
63//!
64//! assert_eq!(INITIALIZED.load(Ordering::Relaxed), 1);
65//! ```
66//!
67//! ## Static Usage
68//!
69//! The `ctor` macro can be used to create a static variable initialized to a
70//! default value. At startup time, the function is used to initialize the
71//! static variable.
72//!
73//! ```
74//! fn value() -> i32 {
75//!     6
76//! }
77//!
78//! ctor_lite::ctor! {
79//!     unsafe static VALUE: i32 = value();
80//! }
81//!
82//! assert_eq!(*VALUE, 6);
83//! ```
84//!
85//! ## Destructor
86//!
87//! This crate can also be used to run a function at program exit as well. The
88//! `dtor` macro can be used to run a function when the program ends.
89//!
90#![cfg_attr(not(miri), doc = "```")]
91#![cfg_attr(miri, doc = "```no_run")]
92//! use macro_rules_attribute::apply;
93//!
94//! #[apply(ctor_lite::dtor!)]
95//! unsafe fn run_at_exit() {
96//!     do_some_cleanup();
97//! }
98//!
99//! # fn do_some_cleanup() {}
100//! ```
101//!
102//! ## Safety
103//!
104//! Macros from this crate must be used with care. In general Rust code is run
105//! with the assumption that no other code is run before program startup, and
106//! no other code is run after program shutdown. Specifically, `libstd` sets up
107//! some global variables before the `main` function and then assumes these
108//! variables are set throughout its runtime. Therefore, calling `libstd`
109//! functions that use these variables will lead to undefined behavior.
110//!
111//! Generally, functions from `core` or `alloc` are safe to call in these
112//! functions. In addition, functions from [`libc`] should be able to be called
113//! freely, as well as most of the functions contained in [`rustix`]. Other
114//! crates should be used only when it is understood what other calls they
115//! contain.
116//!
117//! [`libc`]: https://crates.io/crates/libc
118//! [`rustix`]: https://crates.io/crates/rustix
119//!
120//! In addition, no ordering is guaranteed for functions ran in the `ctor` or
121//! `dtor` macros.
122//!
123//! ## Implementation
124//!
125//! The `ctor` macro works by creating a function with linker attributes that
126//! place it into a special section in the file. When the C runtime starts the
127//! program, it reads function pointers from this section and runs them.
128//!
129//! This function call...
130//!
131//! ```
132//! ctor_lite::ctor! {
133//!     unsafe fn foo() { /* ... */ }
134//! }
135//! ```
136//!
137//! ...is translated to code that looks like this:
138//!
139//! ```
140//! #[used]
141//! #[cfg_attr(any(target_os = "linux", target_os = "android"), link_section = ".init_array")]
142//! #[cfg_attr(target_os = "freebsd", link_section = ".init_array")]
143//! #[cfg_attr(target_os = "netbsd", link_section = ".init_array")]
144//! #[cfg_attr(target_os = "openbsd", link_section = ".init_array")]
145//! #[cfg_attr(target_os = "illumos", link_section = ".init_array")]
146//! #[cfg_attr(any(target_os = "macos", target_os = "ios", target_os = "tvos"), link_section = "__DATA_CONST,__mod_init_func")]
147//! #[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
148//! static FOO: extern fn() = {
149//!   #[cfg_attr(any(target_os = "linux", target_os = "android"), link_section = ".text.startup")]
150//!   extern fn foo() { /* ... */ };
151//!   foo
152//! };
153//! ```
154//!
155//! When creating a global constant with the `ctor` macro it writes code that
156//! runs the function then writes the value into a global constant.
157//!
158//! This code...
159//!
160//! ```
161//! ctor_lite::ctor! {
162//!     unsafe static FOO: i32 = foo();
163//! }
164//! # fn foo() -> i32 { 1 }
165//! ```
166//!
167//! ...is translated to code that looks like this, with modifications that allow
168//! for `FOO` to be used from safe code:
169//!
170//! ```no_compile
171//! static mut FOO: i32 = core::mem::uninitialized();
172//! ctor_lite::ctor! {
173//!     unsafe fn init_storage() {
174//!         FOO = foo();
175//!     }
176//! }
177//! # fn foo() -> i32 { 1 }
178//! ```
179//!
180//! When functions are put into `dtor`, it runs `ctor` with the `libc::atexit`
181//! function to ensure that the function is run at program exit.
182//!
183//! This code...
184//!
185#![cfg_attr(not(miri), doc = "```")]
186#![cfg_attr(miri, doc = "```no_run")]
187//! ctor_lite::dtor! {
188//!     unsafe fn foo() {
189//!         /* ... */
190//!     }
191//! }
192//! ```
193//!
194//! ...is translated to code that looks like this, with modifications that let
195//! us avoid a dependency on the [`libc`] crate:
196//!
197//! ```no_compile
198//! unsafe fn foo() {
199//!     /* ... */
200//! }
201//!
202//! ctor_lite::ctor! {
203//!     unsafe fn run_dtor() {
204//!         libc::atexit(foo);
205//!     }
206//! }
207//! ```
208
209#![no_std]
210
211/// Run a function on program startup or initialize a constant.
212///
213/// See the crate level documentation for more info.
214#[macro_export]
215macro_rules! ctor {
216    // Case 1: Run a function at startup time.
217    (
218        $(#[$meta:meta])*
219        $vis:vis unsafe fn $name:ident () $bl:block
220    ) => {
221        const _: () = {
222            // Fix for issue #2.
223            #[cfg(all(windows, not(miri)))]
224            type Ret = usize;
225            #[cfg(any(not(windows), miri))]
226            type Ret = ();
227
228            $(#[$meta])*
229            $vis unsafe fn $name () {
230                unsafe fn __this_thing_is_always_unsafe() {}
231                __this_thing_is_always_unsafe();
232                $bl
233            }
234
235            #[cfg(not(any(
236                target_os = "linux",
237                target_os = "android",
238                target_os = "freebsd",
239                target_os = "netbsd",
240                target_os = "openbsd",
241                target_os = "dragonfly",
242                target_os = "illumos",
243                target_os = "haiku",
244                target_os = "macos",
245                target_os = "ios",
246                target_os = "visionos",
247                target_os = "tvos",
248                windows
249            )))]
250            compile_error!("ctor! is not supported on the current target");
251
252            #[used]
253            #[allow(non_upper_case_globals, non_snake_case)]
254            #[doc(hidden)]
255            #[cfg_attr(
256                any(target_os = "linux", target_os = "android"),
257                link_section = ".init_array"
258            )]
259            #[cfg_attr(target_os = "freebsd", link_section = ".init_array")]
260            #[cfg_attr(target_os = "netbsd", link_section = ".init_array")]
261            #[cfg_attr(target_os = "openbsd", link_section = ".init_array")]
262            #[cfg_attr(target_os = "dragonfly", link_section = ".init_array")]
263            #[cfg_attr(target_os = "illumos", link_section = ".init_array")]
264            #[cfg_attr(target_os = "haiku", link_section = ".init_array")]
265            #[cfg_attr(
266                any(
267                    target_os = "macos",
268                    target_os = "ios",
269                    target_os = "visionos",
270                    target_os = "tvos"
271                ),
272                link_section = "__DATA,__mod_init_func"
273            )]
274            #[cfg_attr(windows, link_section = ".CRT$XCU")]
275            static __rust_ctor_lite__ctor: unsafe extern "C" fn() -> Ret = {
276                #[cfg_attr(
277                    any(target_os = "linux", target_os = "android"),
278                    link_section = ".text.startup"
279                )]
280                unsafe extern "C" fn ctor() -> Ret {
281                    $name ();
282                    ::core::default::Default::default()
283                }
284
285                ctor
286            };
287        };
288    };
289
290    // Case 2: Initialize a constant at bootup time.
291    (
292        $(#[$meta:meta])*
293        $vis:vis unsafe static $(mut)? $name:ident:$ty:ty = $e:expr;
294    ) => {
295        #[doc(hidden)]
296        #[allow(non_camel_case_types)]
297        $vis struct $name<T> {
298            _data: ::core::marker::PhantomData<T>
299        }
300
301        $(#[$meta:meta])*
302        $vis static $name: $name<$ty> = $name {
303            _data: ::core::marker::PhantomData::<$ty>
304        };
305
306        const _: () = {
307            use ::core::cell::UnsafeCell;
308            use ::core::mem::MaybeUninit;
309            use ::core::ops::Deref;
310
311            struct SyncSlot(UnsafeCell<MaybeUninit<$ty>>);
312            unsafe impl Sync for SyncSlot {}
313
314            static STORAGE: SyncSlot = {
315                SyncSlot(UnsafeCell::new(MaybeUninit::uninit()))
316            };
317
318            impl Deref for $name<$ty> {
319                type Target = $ty;
320
321                fn deref(&self) -> &$ty {
322                    // SAFETY: This will always be initialized.
323                    unsafe {
324                        &*(&*STORAGE.0.get()).as_ptr()
325                    }
326                }
327            }
328
329            $crate::ctor! {
330                unsafe fn init_storage() {
331                    let val = $e;
332
333                    // SAFETY: We are the only ones who can write into STORAGE.
334                    unsafe {
335                        *STORAGE.0.get() = MaybeUninit::new(val);
336                    }
337                }
338            }
339
340            fn __assert_type_is_sync() {
341                fn __must_be_sync<T: Sync>() {}
342                __must_be_sync::<$ty>();
343            }
344        };
345    }
346}
347
348/// Run a function on program shutdown.
349///
350/// See the crate level documentation for more information.
351#[macro_export]
352macro_rules! dtor {
353    (
354        $(#[$meta:meta])*
355        $vis:vis unsafe fn $name:ident () $bl:block
356    ) => {
357        const _: () = {
358            $(#[$meta])*
359            $vis unsafe fn $name () {
360                unsafe fn __this_thing_is_always_unsafe() {}
361                __this_thing_is_always_unsafe();
362                $bl
363            }
364
365            // Link directly to atexit in order to avoid a libc dependency.
366            #[cfg(not(any(
367                target_os = "macos",
368                target_os = "ios",
369                target_os = "visionos",
370                target_os = "tvos"
371            )))]
372            #[inline(always)]
373            unsafe fn __do_atexit(cb: unsafe extern fn()) {
374                extern "C" {
375                    fn atexit(cb: unsafe extern fn());
376                }
377                atexit(cb);
378            }
379
380            // For platforms that have __cxa_atexit, we register the dtor as scoped to dso_handle
381            #[cfg(any(
382                target_os = "macos",
383                target_os = "ios",
384                target_os = "visionos",
385                target_os = "tvos"
386            ))]
387            #[inline(always)]
388            unsafe fn __do_atexit(cb: unsafe extern fn(_: *const u8)) {
389                extern "C" {
390                    static __dso_handle: *const u8;
391                    fn __cxa_atexit(
392                        cb: unsafe extern fn(_: *const u8),
393                        arg: *const u8,
394                        dso_handle: *const u8
395                    );
396                }
397                __cxa_atexit(cb, ::core::ptr::null(), __dso_handle);
398            }
399
400            #[cfg(not(any(
401                target_os = "macos",
402                target_os = "ios",
403                target_os = "visionos",
404                target_os = "tvos"
405            )))]
406            #[cfg_attr(
407                any(
408                    target_os = "linux",
409                    target_os = "android"
410                ),
411                link_section = ".text.exit"
412            )]
413            unsafe extern "C" fn __run_destructor() { $name() };
414            #[cfg(any(
415                target_os = "macos",
416                target_os = "ios",
417                target_os = "visionos",
418                target_os = "tvos"
419            ))]
420            unsafe extern "C" fn __run_destructor(_: *const u8) { $name() };
421
422            $crate::ctor! {
423                unsafe fn register_dtor() {
424                    __do_atexit(__run_destructor);
425                }
426            }
427        };
428    };
429}