dtor 1.0.6

Global, no_std-compatible destructors for all platforms that run after main (like C/C++ __attribute__((destructor)))
Documentation

The crate is part of the linktime project.

GitHub Crates.io License Build Status

crate
linktimedocs.rs crates.io Convenience crate for ctor, dtor and link-section
ctordocs.rs crates.io Module initialization functions before main
dtordocs.rs crates.io Module shutdown functions before main
link-sectiondocs.rs crates.io Linker-managed typed (slices) and untyped sections
scattered-collectdocs.rs crates.io Linker-managed collections: slices, sorted slices, maps

dtor

Shutdown functions for Rust (like __attribute__((destructor)) in C/C++) for Linux, macOS, Windows, mobile (iOS/Android), WASM, BSD/BSD-likes and many other platforms.

use dtor::dtor;
use libc_print::*;

#[dtor(unsafe)]
fn foo() {
    libc_println!("Life after main!");
}

Examples

Print a message at shutdown time.

#[dtor(unsafe)]
fn shutdown() {
    // Using println! or eprintln! here may panic as Rust may have
    // shut down some stdlib services at this time.
    libc_println!("Shutting down!");
}

Platform Support

Platform Link Section at_binary_exit at_module_exit
Linux .fini_array Yes (atexit) Yes (__cxa_atexit)
macOS .mod_term_func 🍎 Yes (atexit) Yes (__cxa_atexit)
Windows .CRT$XPU πŸͺŸ No Yes (atexit)
WASM πŸ•ΈοΈ No Yes No
AIX "Kind of" πŸ”΅ Yes Yes
Other POSIX-like platforms .fini_array/.dtors Yes (atexit) Yes (__cxa_atexit)

Notes:

  • 🍎 Not recommended. Apple platforms no longer call mod_term_func functions.
  • πŸͺŸ Not recommended. Windows platforms may not reliably call functions in link sections, unless a binary is built with a static CRT.
  • πŸ”΅ Link sections are not supported on AIX, but the platform calls functions with the prefix __sinit and __sterm at startup and shutdown respectively. __sterm-prefixed functions are used when the method is specified as linker.
  • πŸ•ΈοΈ WASM wasm-unknown-unknown, wasm-wasip1, wasm-wasip2 are supported.
    • Rust does not currently allow linking into .fini_array sections on WASM, regardless of target, so at_binary_exit is the only supported method on all WASM targets.
    • wasm-unknown-unknown requires host environment support for atexit.
    • wasm-wasip2 may require you to manually call __wasm_call_ctors and __wasm_call_dtors at the appropriate times.

Shutdown Method (#[dtor(method = ...)])

The #[dtor] macro supports multiple registration strategies via #[dtor(method = ...)]. The best choice is platform-dependent:

  • #[dtor] (no method specified): Use the platform's most reliable method: at_module_exit on Windows and Apple platforms, and linker on others.
  • unload: Run on module unload (library unload or process exit) using the platform's default unload method.
  • term: Run on process termination only using the platform's default termination method. Not recommended: code may be unloaded before the dtor runs.
  • at_module_exit: Register using __cxa_atexit (non-Windows) or atexit (Windows) so the dtor runs when the module unloads. Unsupported on WASM.
  • at_binary_exit: Register to run at process exit (unsupported on Windows).
  • linker: Register using the platform's linker mechanism (link_section on all platforms with the exception of export_name_prefix on AIX). Unsupported on Apple platforms.

Default:

  • Apple and Windows default to at_module_exit
  • WASM defaults to at_binary_exit (note that you will need to provide your own atexit implementation for wasm32-unknown-unknown)
  • Most other platforms default to linker

Examples:

use dtor::dtor;
/// Use `at_module_exit` on all platforms
#[dtor(unsafe, method = at_module_exit)]
fn shutdown() {}
use dtor::dtor;

/// Use `link_section` with a section name of `.dtors` on most platforms,
/// and `export_name_prefix` on AIX.
/// 
/// Platform note: this will fail to compile on Apple platforms.
#[dtor(unsafe, method = linker, link_section = ".dtors")]
fn shutdown() {}

Warnings

Rust's philosophy is that nothing happens before or after main and this library explicitly subverts that. The code that runs in the ctor and dtor functions should be careful to limit itself to libc functions and code that does not rely on Rust's stdlib services.

See ::life_before_main for more information.

Under the Hood

The #[dtor] macro effectively creates a constructor that calls libc::atexit with the provided function, i.e. roughly equivalent to:

#[ctor]
fn dtor_atexit() {
    libc::atexit(dtor);
}

Re-exporting from another crate

The macros assume this crate is available as a direct dependency, resolving their support paths through the crate's own name. If you re-export this crate's items as part of your own crate (so that downstream users don't need to depend on it directly), you have two options:

  • (preferred) use the declarative macro form. It resolves its support paths relative to your re-export, so no extra configuration is required.
  • Alternatively, pass the crate_path attribute to redirect the macro's generated output to the path where this crate has been re-exported.

See the crate_path entry in the Macro Attributes section below for the exact syntax for this crate.

Crate Features

Cargo feature Description
proc_macro Enable support for the proc-macro #[dtor] attribute. The declarative form (dtor!(...)) is always available. It is recommended that crates re-exporting the dtor macro disable this feature and only use the declarative form.
std Enable support for the standard library.

Macro Attributes

Do not give the destructor's registration entry a name in the generated code (allows for multiple items with the same name). Equivalent to wrapping the registration in an anonymous const (i.e.: const _ = { ... };).

The path to the dtor crate containing the support macros. If you re-export dtor items as part of your crate, you can use this to redirect the macro's output to the correct crate.

Using the declarative dtor! form is preferred over this parameter.

Specify a custom export name prefix for the generated constructor function.

If specified, an export with the given prefix will be generated in the form:

<prefix>_<unique_id>

Place the generated registration constructor's function pointer in a custom link section.

Specify a custom export name prefix for the destructor function.

If specified, an export with the given prefix will be generated in the form:

<prefix>_<unique_id>

Place the destructor function pointer in a custom link section.

Specify the dtor method.

  • term: Run the dtor on binary termination using the platform's default_term_method. Not recommended as code may be unloaded before the dtor is called.
  • unload: Run the dtor on module unload (library or binary) using the platform's default_unload_method.
  • at_module_exit: Run the dtor using the platform's at_module_exit (__cxa_atexit on all platforms other than Windows, atexit on Windows).
  • at_binary_exit: Run the dtor using the platform's at_binary_exit (unsupported on Windows platforms).
  • linker: Register the dtor using the platform's link_section or export_name_prefix (unsupported on Apple platforms).

Marks a dtor as unsafe. Required.

The dtor crate rejects #[dtor] without marking the item unsafe; that error can be suppressed by passing RUSTFLAGS="--cfg linktime_no_fail_on_missing_unsafe" to Cargo.

Mark generated function pointers used(linker). Requires nightly for the nightly-only feature feature(used_with_arg) (see https://github.com/rust-lang/rust/issues/93798).

This can be made the default by using the cfg flag linktime_used_linker (RUSTFLAGS="--cfg linktime_used_linker").

For a crate using this macro to function correctly with and without this flag, it is recommended to add the following line to the top of lib.rs in the crate root:

#![cfg_attr(linktime_used_linker, feature(used_with_arg))]

Defaults

ctor_export_name_prefix

#[cfg(target_os = "aix")]
ctor_export_name_prefix = "__sinit80000000"

// default
ctor_export_name_prefix = ()

ctor_link_section

#[cfg(target_vendor = "apple")]
ctor_link_section = "__DATA,__mod_init_func,mod_init_funcs"

#[cfg(any(target_os = "linux", target_os = "android", target_os = "freebsd",
target_os = "netbsd", target_os = "openbsd", target_os = "dragonfly",
target_os = "illumos", target_os = "haiku", target_os = "vxworks", target_os =
"nto", target_family = "wasm"))]
ctor_link_section = ".init_array"

#[cfg(target_os = "none")]
ctor_link_section = ".init_array"

#[cfg(target_arch = "xtensa")]
ctor_link_section = ".ctors"

#[cfg(all(target_os = "windows", any(target_env = "gnu", target_env = "msvc")))]
ctor_link_section = ".CRT$XCU"

#[cfg(all(target_os = "windows", not(any(target_env = "gnu", target_env = "msvc"))))]
ctor_link_section = ".ctors"

#[cfg(target_os = "uefi")]
ctor_link_section = ".init_array"

#[cfg(target_os = "aix")]
ctor_link_section = ()

// default
ctor_link_section = ".init_array"

default_term_method

#[cfg(target_os = "windows")]
default_term_method = at_module_exit

// default
default_term_method = at_binary_exit

default_unload_method

// default
default_unload_method = at_module_exit

export_name_prefix

#[cfg(target_os = "aix")]
export_name_prefix = "__sterm80000000"

// default
export_name_prefix = ()

link_section

#[cfg(target_vendor = "apple")]
link_section = "__DATA,__mod_term_func,mod_term_funcs"

#[cfg(any(target_os = "linux", target_os = "android", target_os = "freebsd",
target_os = "netbsd", target_os = "dragonfly", target_os = "illumos",
target_os = "haiku", target_os = "vxworks", target_os = "nto", target_family =
"wasm"))]
link_section = ".fini_array"

#[cfg(target_os = "openbsd")]
link_section = ".dtors"

#[cfg(target_os = "none")]
link_section = ".fini_array"

#[cfg(target_arch = "xtensa")]
link_section = ".dtors"

#[cfg(all(target_os = "windows", any(target_env = "gnu", target_env = "msvc")))]
link_section = ".CRT$XPU"

#[cfg(all(target_os = "windows", not(any(target_env = "gnu", target_env = "msvc"))))]
link_section = ".dtors"

#[cfg(target_os = "uefi")]
link_section = ".fini_array"

#[cfg(target_os = "aix")]
link_section = ()

// default
link_section = ".fini_array"

method

#[cfg(target_vendor = "apple")]
method = at_module_exit

#[cfg(target_os = "windows")]
method = at_module_exit

#[cfg(target_family = "wasm")]
method = at_binary_exit

#[cfg(target_os = "openbsd")]
method = at_module_exit

// default
method = linker

r#unsafe

#[cfg(linktime_no_fail_on_missing_unsafe)]
r#unsafe = (no_fail_on_missing_unsafe)

// default
r#unsafe = ()

used_linker

#[cfg(linktime_used_linker)]
used_linker = used_linker

// default
used_linker = ()