Skip to content

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

Closed

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.

Activity

  1. added
    A-docsArea: Documentation for any part of the project, including the compiler, standard library, and tools
    T-libsRelevant to the library team, which will review and decide on the PR/issue.
    on Apr 23, 2023
  2. ChrisDenton commented on Apr 23, 2023

    @ChrisDenton
    Member

    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:

    • core is fine
    • alloc depends on the allocator used
    • std assumes code is running in main (except for the core and possibly alloc stuff)
  3. the8472 commented on Apr 23, 2023

    @the8472
    Member

    IMHO, core types should work in whatever context but libstd is more dicey

    Core types can still panic and panics do IO.

  4. ChrisDenton commented on Apr 23, 2023

    @ChrisDenton
    Member

    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?

  5. the8472 commented on Apr 23, 2023

    @the8472
    Member

    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.

  6. ChrisDenton commented on Apr 24, 2023

    @ChrisDenton
    Member

    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.

  7. the8472 commented on Apr 24, 2023

    @the8472
    Member

    Could we turn panics into immediate aborts after main? That would also avoid the backtrace printing and symbolication.

  8. Amanieu commented on May 31, 2023

    @Amanieu
    Member

    I looked through the standard library, and thread:current is 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 from thread::park, mpmc and thread::scope, which can therefore panic when called during TLS destruction. mpmc and thread::scoped might be able to be reworked to avoid this, but this is fundamentally a limitation of park since it requires a reference to the current Thread to 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 a Drop this could be moved to a separate thread_local to properly support stack overflow handling during TLS destruction.

  9. added and removed
    I-libs-nominatedNominated for discussion during a libs team meeting.
    on Jun 28, 2023
  10. the8472 commented on Jul 12, 2023

    @the8472
    Member

    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.

    • before/after main behavior is not tested. It is best-effort and if someone relies on it they should have their own tests
    • core and alloc are expected to work because they don't touch OS APIs or global state
    • caveat: user code in any hookable API like panic handlers, global allocators, OOM handler or any future hooks. Especially panics affect a lot of code.
      • we might replace hooks after main?
    • most existing parts of std, backtrace and other things interacting with the OS are expected to work but there may be platform-specific edge-cases and the behavior may change in the future due to changing implementation details or new features that depend more runtime state (e.g. a std threadpool or async runtime)
    • then finally a list of known limitations
  11. the8472 commented on Aug 26, 2023

    @the8472
    Member

    Proposed documentation based on my previous comment: #115247

  12. linked a pull request that will close this issueDocument std limitations before/after main #115247on Aug 26, 2023
  13. added a commit that references this issue on Sep 16, 2023
  14. added a commit that references this issue on Sep 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-docsArea: Documentation for any part of the project, including the compiler, standard library, and toolsT-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