@dtolnay I couldn’t find anything in the docs for typetag, inventory or ctor on why any of this needs to be implemented with life-before/after-main instead of making the client app write something like typetag::init() in main().
Is the life-before-main stuff “just” to enable writing libraries that don’t virally “infect” all transitive dependents and their client apps with a need to explicitly init() the library in main()? Could this be controlled by cargo features? Is the inadequacy of cargo features for transitive dependency config (ping @kornel) a factor here? etc.
Cargo features are separate from Rust language features, and Cargo can’t nicely set language features (overriding compiler flags for the entire build is a blunt tool), so there’s a hypothetical inadequacy, but I don’t think that’s the reason.
Not having code before main is a very useful property for a low level language.
It means it’s easily interoperable with C. It means it can be used in a static library linked with any other programming language. It means it can work easily as a dynamic library used as a plug-in in lots of native software, and in all these cases every function you export from Rust will just work.
If Rust could depend on some global code automagically run outside of main, then it could break if that magic isn’t always properly set up. It would mean that externally calling some random Rust function (from C, from other language’s FFI, from plug-in host) could hit Rust by surprise in a semi-uninitialized state with broken globals.
OP’s motivation: running env_logger::init() for every test is a real problem. BUT I think it’s better to solve that one with test frameworks that have setup and teardown, without adding a potentially problematic feature to the entire language: