The idea is to keep this simple, deterministic and optional. Is it sufficiently useful to be worth adding to Rust?
One attribute is added: #[init]
. (There could instead be two, one for marking functions and one for use with extern
.)
Motivation: make the env_logger
crate easier to use, especially in “integration” test suites. It could also provide a work-around for producing complex constants at run-time instead of compile-time where run-time function evaluation is needed.
Variations: a similar strategy for de-init/clean-up could be used, but I’m not sure what the real use-cases are.
Caveats: using this to initialise a logger for in-code test functions without also initialising for other uses won’t work for this approach. Maybe an option like #[init(tests_only)] extern crate ...;
is needed (which can also be used in libraries); alternatively the entire depency may be optional (e.g. #[cfg(test)] #[init] extern crate env_logger;
).
Function marker: the #[init]
attribute can be used to mark special functions. This can only be used on public (externally visible) library functions, which guarantees that the functions can be called manually instead if preferred. Example:
// in a 'greeter' lib:
#[init]
pub fn greet() {
println!("Hello, world");
}
Extern marker: when an external dependency is declared via extern
, the #[init]
attribute can be used to call all of its init-attributed functions. There is no transitive-initialisation: libraries cannot use #[init]
on extern statements (this may mean executables have to add extra extern statements). Example:
// in an executable:
#[init]
extern crate greeter;
Deterministic effects: when an ‘extern’ library is declared with ‘init’, its init-attributed functions are called in the order they are found, e.g. the order init1, init2, init3
below:
// a library
#[init]
pub fn init1() {}
pub mod X {
#[init]
pub fn init2() {}
}
#[init]
pub fn init3() {}
#[init]
can only be used on extern
statements within one module (the executable’s root module); if more than one of these uses #[init]
they are initialised in the order encountered. This happens immediately before main
gets called or the first #[test]
function is run. Example:
// executable
#[init] extern crate lib1;
#[init] extern crate lib2;
// runs immediately after lib2's last #[init] function
fn main() {}
If a function is called via #[init]
and it is called directly (e.g. from main
), it is called two (or more) times (if this is undesirable, the user should avoid doing both).