Skip to content

Consider documenting that (parts of?) stdlib must not be used before/after main #110708

Closed
@dtolnay

Description

@dtolnay

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-docsArea: Documentation for any part of the project, including the compiler, standard library, and toolsS-waiting-on-teamStatus: Awaiting decision from the relevant subteam (see the T-<team> label).T-libsRelevant to the library team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions