-
Notifications
You must be signed in to change notification settings - Fork 52
FAQ: Life‐before and life‐after main
The #[ctor] macro in Rust enables the definition of functions that execute during a program’s initialization (before main) or termination (after main). This functionality mirrors the attribute((constructor)) and attribute((destructor)) attributes in C/C++, allowing developers to set up or clean up resources at the boundaries of program execution.
In Rust, the execution of code before main is unconventional, as the language emphasizes explicit initialization within the main function. However, there are scenarios where pre-main execution is beneficial, such as:
- Initializing global state: Setting up global variables or resources that need to be ready before any function executes.
- Registering plugins or modules: Automatically registering components upon program start without explicit calls.
The #[ctor] macro facilitates these use cases by marking functions to run during the program’s startup sequence.
Example: Pre-main Initialization
use ctor::ctor;
use std::sync::atomic::{AtomicBool, Ordering};
static INITED: AtomicBool = AtomicBool::new(false);
#[ctor]
fn initialize() {
INITED.store(true, Ordering::SeqCst);
}
In this example, the initialize function sets an AtomicBool
to true before main executes, ensuring that the program recognizes the initialization state immediately.
Similarly, executing code after main concludes is atypical in Rust, as the language relies on the Drop trait for cleanup tasks. Nonetheless, certain situations may require actions post-main, such as:
- Flushing logs: Ensuring that all logging data is written out before the program exits.
- Releasing non-Rust resources: Cleaning up resources not managed by Rust’s ownership system.
- Non-Rust library cleanup.
The #[dtor] macro allows developers to define functions that run during the program’s termination sequence.
Example: Post-main Cleanup
use ctor::dtor;
use libc_print::libc_println;
#[dtor]
fn cleanup() {
unsafe {
libc_println!("Program is terminating.");
}
}
Here, the cleanup function executes after main has finished, printing a termination message. Notably, it uses libc_println! from the libc_print crate because standard output facilities like println! may be unavailable during shutdown.
While the #[ctor] and #[dtor] macros offer powerful capabilities, they come with caveats:
Standard Library Availability: During the execution of constructor (#[ctor]) and destructor (#[dtor]) functions, parts of Rust’s standard library may not be fully operational. For instance, using println! in a destructor can cause a panic. The libc_print crate provides macros like libc_println! that interact directly with libc functions, offering a safer alternative for output in these contexts.
Tokio Compatibility: The ctor crate is known to be incompatible with the Tokio asynchronous runtime. Using #[ctor] or #[dtor] macros in conjunction with Tokio can lead to undefined behavior and is not recommended.