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}