Attribute Macro ctor

Source
#[ctor]
Expand description

Marks a function or static variable as a library/executable constructor. This uses OS-specific linker sections to call a specific function at load time.

§Important notes

Rust does not make any guarantees about stdlib support for life-before or life-after main. This means that the ctor crate may not work as expected in some cases, such as when used in an async runtime or making use of stdlib services.

Multiple startup functions/statics are supported, but the invocation order is not guaranteed.

The ctor crate assumes it is available as a direct dependency, with extern crate ctor. If you re-export ctor items as part of your crate, you can use the crate_path parameter to redirect the macro’s output to the correct crate.

§Attribute parameters

  • crate_path = ::path::to::ctor::crate: The path to the ctor crate containing the support macros. If you re-export ctor items as part of your crate, you can use this to redirect the macro’s output to the correct crate.
  • used(linker): (Advanced) Mark the function as being used in the link phase.
  • link_section = "section": The section to place the constructor in.
  • anonymous: Do not give the constructor a name in the generated code (allows for multiple constructors with the same name).

§Examples

Print a startup message (using libc_print for safety):

use libc_print::std_name::println;

#[ctor]
unsafe fn foo() {
  // Using libc_print which is safe in `#[ctor]`
  println!("Hello, world!");
}

println!("main()");

Make changes to static variables:

static INITED: AtomicBool = AtomicBool::new(false);

#[ctor]
unsafe fn set_inited() {
  INITED.store(true, Ordering::SeqCst);
}

Initialize a HashMap at startup time:

#[ctor]
pub static STATIC_CTOR: HashMap<u32, String> = unsafe {
  let mut m = HashMap::new();
  for i in 0..100 {
    m.insert(i, format!("x*100={}", i*100));
  }
  m
};

§Details

The #[ctor] macro makes use of linker sections to ensure that a function is run at startup time.

#[ctor]

unsafe fn my_init_fn() {
  /* ... */
}

The above example translates into the following Rust code (approximately):

#[used]
#[cfg_attr(target_os = "linux", link_section = ".init_array")]
#[cfg_attr(target_vendor = "apple", link_section = "__DATA,__mod_init_func")]
#[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
/* ... other platforms elided ... */
static INIT_FN: extern fn() = {
    extern fn init_fn() { my_init_fn(); };
    init_fn
};

For static items, the macro generates a std::sync::OnceLock that is initialized at startup time.

#[ctor]
static FOO: HashMap<u32, String> = unsafe {
  let mut m = HashMap::new();
  for i in 0..100 {
    m.insert(i, format!("x*100={}", i*100));
  }
  m
};

The above example translates into the following Rust code (approximately), which eagerly initializes the HashMap inside a OnceLock at startup time:

static FOO: FooStatic = FooStatic { value: ::std::sync::OnceLock::new() };
struct FooStatic {
  value: ::std::sync::OnceLock<HashMap<u32, String>>,
}

impl ::std::ops::Deref for FooStatic {
  type Target = HashMap<u32, String>;
  fn deref(&self) -> &Self::Target {
    self.value.get_or_init(|| unsafe {
      let mut m = HashMap::new();
      for i in 0..100 {
        m.insert(i, format!("x*100={}", i*100));
      }
      m
    })
  }
}

#[ctor]
unsafe fn init_foo_ctor() {
  _ = &*FOO;
}