Ability to determine if current thread is the main thread of process

What is being asked is a standardized, low level building mechanism to determine whether the current thread was the first thread to run (a concept that is well defined in any platform, even those that don't care about it).

What this would be used for is platform-dependent: is_ui_thread is a higher level concept that isn't always applicable. It's very common for the Rust language to expose low level building blocks in the stdlib, and let other crates build upon it to provide higher level concepts.

A framework can be as opinionated as it needs (or wants) to be but std does not have the same luxury. A best effort is_main_thread is fine for an opinionated guard against misuse. The framework can simply say "you're doing it wrong" if you go outside the lines.

Many platforms do not have a low-level building mechanism for determine the main thread. To do this reliably on Windows you'd have to loop through every thread, finding the one with earliest creation time, and assume that's the main thread. And that could still be wrong.

Look, I'm not at all against an is_main_thread function but I'm really struggling to see why this belongs in the standard library instead of a crate.

2 Likes

That said, if you do want this in the standard library then I'd recommend opening an API Change Proposal (ACP) on the library team's repository

And basically everyone advocating for a std is_main_thread is saying that it should be a best-effort deal only guaranteed to return a positive result when the Rust main entrypoint was used. Identifying the main thread when Rust doesn't control the main thread would be clearly documented to be unreliable. It might even be prudent to only support identifying the Rust-entered main thread (unless unsafely setting the main thread identity, which would probably be desirable.)

This should be in the standard library because it needs to happen at sys startup time, when the Rust runtime names the main thread. This can't be in a 3rd party crate because of the platforms that don't provide a way to identify the main thread. On such platforms the only way to identify the main thread is to inject startup code to remember the main thread, which is a superpower only available to std.

Ah so rather than the main thread, what you want is something that tells you if Rust's main was run on this thread? That's more doable but as you say, would rule out use in native static or dynamic libraries. You can get a similar effect by like calling, for example, a framework::init function from main.

Your suggestion for a function that returns io::Result<Thread> for the thread that ran Rust main is, I think, much better than returning a bool as it's much clearer that a failure to get the thread does not necessarily indicate much of anything.

2 Likes

If this is not 0-cost anyway, why not do this with this 10-line lib? Most good lang features start out as crates. Maybe ctor could even help you with removing the first line in fn main

playground

static MAIN_FN_THREAD: OnceLock<ThreadId> = OnceLock::new();

fn hi_this_is_main_fn_thread() {
    MAIN_FN_THREAD.set(thread::current().id()).unwrap()
}

fn is_this_thread_the_main_fn_thread() -> bool {
    MAIN_FN_THREAD.get().unwrap() == &thread::current().id()
}

fn main() {
    // register the thread that runs `fn main`
    hi_this_is_main_fn_thread();

    // now query if the this thread is the one that runs `fn main`
    println!("{}", is_this_thread_the_main_fn_thread());
    std::thread::spawn(|| {
        println!("{}", is_this_thread_the_main_fn_thread());
    }).join().unwrap();
}

That makes it useless for winit which needs to know the thread on which the ui event loop is allowed, not the rust main thread. It did still need a fallback for if the rust main function is never used and on some OSes where the main thread and ui thread are different checking for the main thread would make it impossible to use winit as either winit or the ui framework will reject the thread no matter which one is used.

4 Likes

On Windows, and most platforms, all we need to do is have the language runtime (= std::rt) save the result of GetCurrentThreadId() or the platform equivalent to a global variable once, and then return GetCurrentThreadId() == MAIN_THREAD_ID). on some platforms you can even just do it without saving anything, like on Linux/unix where you gave gettid() == getpid()

People mentioned Android, where my example framework, SDL, spawns its own thread. Since this (SDL_main) logic would be bypassed in Rust (although I've already been talking to the SDL people about making the platform setup code more easily accessible from other languages), the main thread checking logic would still be useful for setting up the thread ourselves, and beyond this, like one person mentioned, having a slightly overly-strict restriction is sometimes better than having a potentially flaky/inconsistent restriction.

as for "why does this need to be in std" because std calls main() and that's the only way to make this mechanism behave reliably/work at all (like other people mentioned, the is_main_thread crate based on winit actually doesn't work in some cases such as creating dylibs) and we don't want to put the onus of identifying the main thread on the end user.

1 Like

Even if in std, MAIN_THREAD_ID will still never be set in dynamic or native static libraries. std::rt is not really different to the user main function in that regard.

1 Like

I don't understand. You can still call env::args() in a library despite it relying on std::rt. Because eventually that library will be linked into a binary that will have a main() function and the relevant runtime machinery.

env::args() works even if std::rt is never run. E.g. https://github.com/rust-lang/rust/blob/f320f42c593b4353ea47448805f009ca0646cb06/library/std/src/sys/unix/args.rs#L107. The standard library does not (and cannot) rely on std::rt actually being called. That said, depending on the target, it might rely on the C runtime to some extent.

On glibc and Linux. for other supported unix platforms this impl does not apply and as the comment states, needs to be supplied by the startup (i.e. runtime). So if "doesn't work in dylibs on some platforms" is valid for env::args, it's valid for this proposed thread API. which would realistically only be called in executables/statically linked crates anyhow

Right which is why I said it has the same limitations as user main. And why I would be wary of a function in std that simply returns a bool when the answer is not known.

For staticlibs std::rt::init isn't called either and in any case several targets supported by winit actually require the rust code to be compiled as either cdylib (Android) or staticlib (iOS).

1 Like

Main threads are just like any other thread, sometimes the main thread doesn't even have to be the starting thread. And the starting thread can just end after it has spawned other threads. (tokio::main)

And crates also can't rely on this function, as its not clear what the main thread actually is.

Some APIs require you to call them from a single and the same thread, which you could argue is the main thread, but it's something the crate author or you should take into consideration of ensuring it, as it depends on the context.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.