Repository navigation
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.
Activity
ChrisDenton commentedon Apr 23, 2023
IMHO, core types should work in whatever context but libstd is more dicey. It more explicitly has a runtime or three (e.g. Rust's, libc's and the OS's). So at a minimum anything in the modules
env,fs,io,net,os,process,thread, etc should be regarded with suspicion unless documented otherwise.In summary, my thoughts are:
coreis fineallocdepends on the allocator usedstdassumes code is running in main (except for thecoreand possiblyallocstuff)the8472 commentedon Apr 23, 2023
Core types can still panic and panics do IO.
ChrisDenton commentedon Apr 23, 2023
Hm... how much of an issue is that? Panics don't have to do I/O (e.g. if stderr is closed) so if platforms don't support that before or after main then it can be silently skipped, no?
the8472 commentedon Apr 23, 2023
But do we do that properly today? And it's not just IO but also panic hooks which in turn can rely on various other parts of std.
ChrisDenton commentedon Apr 24, 2023
I think it would be fine to document that e.g. after main they may want to set an empty (or aborting) panic hook if panicking is possible.
I really don't want to be in a situation where the user is in a worse place than if they'd just used
no_std.the8472 commentedon Apr 24, 2023
Could we turn panics into immediate aborts after main? That would also avoid the backtrace printing and symbolication.
Amanieu commentedon May 31, 2023
I looked through the standard library, and
thread:currentis the only function that has limitation on usage before/after main (in this case, this is only a problem during TLS destruction). This function is also called fromthread::park,mpmcandthread::scope, which can therefore panic when called during TLS destruction.mpmcandthread::scopedmight be able to be reworked to avoid this, but this is fundamentally a limitation ofparksince it requires a reference to the currentThreadto work.The stack guard used by the stack overflow handler also accesses the same TLS slot as
thread::current, but since the stack bounds don't have aDropthis could be moved to a separatethread_localto properly support stack overflow handling during TLS destruction.the8472 commentedon Jul 12, 2023
If we're going to make a statement at all then imo we should start out with something more cautious than making solid guarantees since this would be a pretty wide-ranging promise.
the8472 commentedon Aug 26, 2023
Proposed documentation based on my previous comment: #115247
Rollup merge of rust-lang#115247 - the8472:life-before-main, r=dtolnay
Unrolled build for rust-lang#115247