re_capabilities/
main_thread_token.rs

1use static_assertions::assert_not_impl_any;
2
3/// A token that (almost) proves we are on the main thread.
4///
5/// Certain operations are only allowed on the main thread.
6/// These operations should require this token.
7/// For instance, any function using file dialogs (e.g. using [`rfd`](https://docs.rs/rfd/latest/rfd/)) should require this token.
8///
9/// The token should only be constructed in `fn main`, using [`MainThreadToken::i_promise_i_am_on_the_main_thread`],
10/// and then be passed down the call tree to where it is needed.
11/// [`MainThreadToken`] is neither `Send` nor `Sync`,
12/// thus guaranteeing that it cannot be found in other threads.
13///
14/// Of course, there is nothing stopping you from calling [`MainThreadToken::i_promise_i_am_on_the_main_thread`] from a background thread,
15/// but PLEASE DON'T DO THAT.
16/// In other words, don't use this as a guarantee for unsafe code.
17///
18/// There is also [`MainThreadToken::from_egui_ui`] which uses the implicit guarantee of egui
19/// (which _usually_ is run on the main thread) to construct a [`MainThreadToken`].
20/// Use this only in a code base where you are sure that egui is running only on the main thread.
21#[derive(Clone, Copy)]
22pub struct MainThreadToken {
23    /// Prevent from being sent between threads.
24    ///
25    /// Workaround until `impl !Send for X {}` is stable.
26    _dont_send_me: std::marker::PhantomData<*const ()>,
27}
28
29impl MainThreadToken {
30    /// Only call this from `fn main`, or you may get weird runtime errors!
31    pub fn i_promise_i_am_on_the_main_thread() -> Self {
32        // On web there is no thread name.
33        // On native the thread-name is always "main" in Rust,
34        // but there is nothing preventing a user from also naming another thread "main".
35        // In any case, since `MainThreadToken` is just best-effort, we only check this in debug builds.
36        #[cfg(not(target_arch = "wasm32"))]
37        debug_assert_eq!(
38            std::thread::current().name(),
39            Some("main"),
40            "DEBUG ASSERT: Trying to construct a MainThreadToken on a thread that is not the main thread!"
41        );
42
43        Self {
44            _dont_send_me: std::marker::PhantomData,
45        }
46    }
47
48    /// We _should_ only create an [`egui::Ui`] on the main thread,
49    /// so having it is good enough to "prove" that we are on the main thread.
50    ///
51    /// Use this only in a code base where you are sure that egui is running only on the main thread.
52    ///
53    /// In theory there is nothing preventing anyone from creating a [`egui::Ui`] on another thread,
54    /// but practice that is unlikely (or intentionally malicious).
55    #[cfg(feature = "egui")]
56    pub fn from_egui_ui(_ui: &egui::Ui) -> Self {
57        Self::i_promise_i_am_on_the_main_thread()
58    }
59}
60
61assert_not_impl_any!(MainThreadToken: Send, Sync);
62assert_not_impl_any!(&MainThreadToken: Send, Sync);