ctor_lite/
lib.rs

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