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}