windows_helpers/
cell.rs

1use std::{
2    cell::{Cell, Ref, RefCell, RefMut},
3    panic::{self, AssertUnwindSafe},
4};
5
6/// A `RefCell` that allows to recursively retrieve a mutable reference.
7///
8/// Like [`std::cell::RefCell`], but with an additional [`Self::borrow_mut_reentrant()`] method. (If needed, the type could call through to more of `RefCell`'s other methods.)
9pub struct ReentrantRefCell<T: ?Sized> {
10    num_mut_re_borrows: Cell<usize>,
11    // `RefCell` not implementing `Sync` will make the struct not implement it either.
12    ref_cell: RefCell<T>,
13}
14
15// Like for `RefCell`.
16unsafe impl<T: ?Sized> Send for ReentrantRefCell<T> where T: Send {}
17
18impl<T> ReentrantRefCell<T> {
19    pub fn new(data: T) -> Self {
20        ReentrantRefCell {
21            num_mut_re_borrows: Cell::new(0),
22            ref_cell: RefCell::new(data),
23        }
24    }
25
26    pub fn borrow(&self) -> Ref<T> {
27        #![inline]
28
29        self.ref_cell.borrow()
30    }
31
32    pub fn borrow_mut(&self) -> RefMut<T> {
33        #![inline]
34
35        self.ref_cell.borrow_mut()
36    }
37
38    pub unsafe fn borrow_mut_reentrant<F, U>(&self, f: F) -> U
39    where
40        F: FnOnce(&mut T) -> U,
41    {
42        //!  Mutably borrows the wrapped value for the duration of the closure call, again allowing mutable borrows by means of this method inside of the closure and the functions it calls.
43        //!
44        //! It borrows with [`std::cell::RefCell::borrow_mut()`] for the outermost call, which means additional attempts to borrow during the outermost borrow, other than by means of this method, will panic. Repeated inner calls provide the mutable reference that the outermost call made available.
45        //!
46        //! The function is useful when dealing with an FFI and foreign code calls into your callback with a [`ReentrantRefCell`] at hand, you then call an FFI function and, during this call, the foreign code calls into your callback again. This happens, e.g., with [window procedures][1] on Windows when calling functions like [`DestroyWindow()`][2] or [`MoveWindow()`][3] in the procedure itself.
47        //!
48        //! # Safety
49        //! You are responsible to only call reentrance causing functions (like FFI functions) as if they had a `&mut self` parameter and wouldn't cause a compiler error with that. I.e., you must, e.g., not borrow something mutably from the mutable reference you get, call the reentrance causing function and then continue to use the borrow from before. When used in the relevant cases, a helper function that simply demands a `&mut self` parameter and just calls through to the closure from its second parameter would desirably trigger compiler errors. It's unknown whether using such a helper function is necessary with regard to possible compiler optimizations when not using it.
50        //!
51        //! Searching for "sen" ("send"/"sent") on Windows API function doc pages seems to be a good way to check whether a function may synchronously call the window procedure.
52        //!
53        //! [1]: https://learn.microsoft.com/en-us/windows/win32/winmsg/window-procedures
54        //! [2]: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-destroywindow
55        //! [3]: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-movewindow
56
57        let prev_num_mut_re_borrows = self
58            .num_mut_re_borrows
59            .replace(self.num_mut_re_borrows.get() + 1);
60
61        // Because a caught panic is resumed below, unwind safety can be asserted. (A panic in the middle of mutating the data or whatever the closure closes over may leave some data broken. But since the panic is resumed, it's as if the panic wasn't caught.)
62        let f_retval = panic::catch_unwind(AssertUnwindSafe(|| {
63            if prev_num_mut_re_borrows == 0 {
64                f(&mut *self.ref_cell.borrow_mut())
65            } else {
66                f(&mut *self.ref_cell.as_ptr())
67            }
68        }));
69
70        self.num_mut_re_borrows.replace(prev_num_mut_re_borrows);
71
72        f_retval.unwrap_or_else(|panic_payload| panic::resume_unwind(panic_payload))
73    }
74}