Description
All existing standard library documentation implicitly assumes that the APIs are being used between the start of a Rust main
and end of main
.
For example std::thread::current
does not document any indication that the function would panic. It does not need to document that, because the function cannot panic, as long as the call occurs within the duration of main
.
However it's possible to observe a panic like this:
extern "C" fn get_thread() {
let _ = std::panic::catch_unwind(std::thread::current);
}
fn main() {
unsafe { libc::atexit(get_thread) };
}
thread '<unnamed>' panicked at 'use of std::thread::current() is not possible after the thread's local data has been destroyed', library/std/src/thread/mod.rs:733:5
stack backtrace:
5: core::option::Option<T>::expect
at /rustc/90743e7298aca107ddaa0c202a4d3604e29bfeb6/library/core/src/option.rs:741:21
6: std::thread::current
at /rustc/90743e7298aca107ddaa0c202a4d3604e29bfeb6/library/std/src/thread/mod.rs:733:5
7: core::ops::function::FnOnce::call_once
at /rustc/90743e7298aca107ddaa0c202a4d3604e29bfeb6/library/core/src/ops/function.rs:251:5
11: std::panic::catch_unwind
at /rustc/90743e7298aca107ddaa0c202a4d3604e29bfeb6/library/std/src/panic.rs:137:14
12: playground::get_thread
at ./[src/main.rs:2](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021#):13
14: exit
15: __libc_start_main
16: _start
(Related PR and discussion: #107216)
In general using the standard library from an atexit
callback, or before main
through a static constructor, is UB: according to #107216 (comment) "we can't really guarantee anything specific happens [...]; at least not in a cross-platform way."
Is this worth calling out centrally as a caveat to all other documentation of the standard library? At the top level of the whole std
crate (it would perhaps be more prominent than it deserves), at the module level, or in the Reference? Certainly for std::thread
, std::io
, std::fs
, the expectation users need to have is that nothing in there will work outside of main
.
Are there APIs it makes sense to carve out as being permissible outside of main
? Stuff like Cell
, ManuallyDrop
, MaybeUninit
, NonNull
, etc. We'd maybe need to do research into how constructors and atexit are being used in the wild. For example the inventory
crate relies on AtomicPtr
, UnsafeCell
, and Option
to be usable before main
: https://github.com/dtolnay/inventory/blob/508cb5918640d05414b0c49843d1c26088df6713/src/lib.rs#L191. It seems obvious that those things should work but there isn't documentation which guarantees it. I assume that makes the inventory
crate technically unsound as written.