Skip to main content

qtbridge_interfaces/object_access/
rust_object_access.rs

1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only
3
4use std::mem;
5use std::rc::Rc;
6use std::cell::{Cell, RefCell};
7
8#[macro_export]
9macro_rules! call_rust_trait_impl {
10    (unsafe mut $self:expr, $method:ident ( $($arg:expr),* )) => {
11        $self.rust_obj
12            .try_call_rust_with_handle_mut(|vtable| {
13                unsafe { vtable.$method($($arg),*) }
14            })
15            .expect(concat!(
16                "Failed to borrow mutably for ",
17                stringify!($method)
18            ))
19    };
20
21    (unsafe $self:expr, $method:ident ( $($arg:expr),* )) => {
22        $self.rust_obj
23            .try_call_rust_with_handle(|vtable| {
24                unsafe { vtable.$method($($arg),*) }
25            })
26            .expect(concat!(
27                "Failed to borrow for ",
28                stringify!($method)
29            ))
30    };
31
32    (mut $self:expr, $method:ident ( $($arg:expr),* )) => {
33        $self.rust_obj
34            .try_call_rust_with_handle_mut(|vtable| {
35                vtable.$method($($arg),*)
36            })
37            .expect(concat!(
38                "Failed to borrow mutably for ",
39                stringify!($method)
40            ))
41    };
42
43    ($self:expr, $method:ident ( $($arg:expr),* )) => {
44        $self.rust_obj
45            .try_call_rust_with_handle(|vtable| {
46                vtable.$method($($arg),*)
47            })
48            .expect(concat!(
49                "Failed to borrow for ",
50                stringify!($method)
51            ))
52    };
53}
54
55#[macro_export]
56macro_rules! call_cpp_impl {
57    (mut $self:expr, $mut_reference:expr, $method:ident ( $($arg:expr),* )) => {{
58        let proxy = unsafe {
59            $self.cpp_proxy
60                .as_mut()
61                .expect("cpp_proxy was null")
62        };
63        let proxy_pinned = unsafe { std::pin::Pin::new_unchecked(proxy) };
64        $self.rust_obj
65            .try_store_handle_and_call_cpp_mut($mut_reference, || proxy_pinned.$method($($arg),*))
66            .expect(concat!(
67                "Failed to borrow mutably for ",
68                stringify!($method)
69            ))
70    }};
71
72    ($self:expr, $reference:expr, $method:ident ( $($arg:expr),* )) => {{
73        let proxy = unsafe {
74            $self.cpp_proxy
75                .as_ref()
76                .expect("cpp_proxy was null")
77        };
78         $self.rust_obj
79            .try_store_handle_and_call_cpp($reference, || proxy.$method($($arg),*))
80            .expect(concat!(
81                "Failed to borrow for ",
82                stringify!($method)
83            ))
84    }};
85}
86
87/// Manages shared access to a Rust object across the Rust/C++ boundary.
88///
89/// When Rust calls into C++ and C++ calls back into Rust, the inner callback
90/// cannot re-borrow the `RefCell` without panicking. This struct solves
91/// this by caching a raw pointer before crossing into C++ and restoring it as
92/// a reference on the way back, avoiding a second borrow while preserving a
93/// valid borrow stack per the "Stacked Borrows" aliasing model.
94pub struct RustObjAccess<T: ?Sized> {
95    /// The strong reference that keeps the Rust object alive: a value
96    /// lives exactly as long as its proxy pair, plus any user handles.
97    shared_reference: Rc<RefCell<T>>,
98    borrow: Cell<BorrowState<T>>,
99}
100
101impl<T: ?Sized> RustObjAccess<T> {
102    pub fn new(ptr: Rc<RefCell<T>>) -> Self {
103        Self {
104            shared_reference: ptr,
105            borrow: Cell::new(BorrowState::None),
106        }
107    }
108
109    pub fn try_call_rust_with_handle<F, R>(&self, f: F) -> Result<R, RustObjAccessError>
110    where
111        F: FnOnce(&T) -> R,
112    {
113        let guard = BorrowState::consume(&self.borrow);
114
115        match guard.content() {
116            BorrowState::Immutable(ptr) => Ok(f(unsafe { &**ptr })),
117            BorrowState::Mutable(ptr) => Ok(f(unsafe { &**ptr })),
118            BorrowState::None => {
119                // Protect from a garbage collection.
120                let rc = self.shared_reference.clone();
121                let ref_guarded = rc.try_borrow()
122                    .map_err(RustObjAccessError::BorrowError)?;
123                Ok(f(&*ref_guarded))
124            }
125        }
126    }
127
128    pub fn try_call_rust_with_handle_mut<F, R>(&self, f: F) -> Result<R, RustObjAccessError>
129    where
130        F: FnOnce(&mut T) -> R,
131    {
132        let guard = BorrowState::consume(&self.borrow);
133
134        match guard.content() {
135            BorrowState::Mutable(ptr) => Ok(f(unsafe { &mut **ptr })),
136            BorrowState::Immutable(_) => Err(RustObjAccessError::BorrowConflict),
137            BorrowState::None => {
138                // Protect from a garbage collection.
139                let rc = self.shared_reference.clone();
140                let mut ref_guarded = rc.try_borrow_mut()
141                    .map_err(RustObjAccessError::BorrowMutError)?;
142                Ok(f(&mut *ref_guarded))
143            }
144        }
145    }
146
147    pub fn try_store_handle_and_call_cpp<F, R>(&self, rust_obj: &T, f: F) -> Result<R, RustObjAccessError>
148    where
149        F: FnOnce() -> R,
150    {
151        assert!(
152            self.contains(rust_obj),
153            "The rust_obj you want to call a function on does not match the shared reference."
154        );
155        let guard = BorrowState::store(&self.borrow, rust_obj);
156        // It is possible that multiple immutable references are alive, but no mutable
157        if matches!(guard.content(), BorrowState::Mutable(_)) {
158            return Err(RustObjAccessError::BorrowConflict);
159        }
160        Ok(f())
161    }
162
163    pub fn try_store_handle_and_call_cpp_mut<F, R>(&self, rust_obj: &mut T, f: F) -> Result<R, RustObjAccessError>
164    where
165        F: FnOnce() -> R,
166    {
167        assert!(
168            self.contains(rust_obj),
169            "The rust_obj you want to call a function on does not match the shared reference."
170        );
171        let guard = BorrowState::store_mut(&self.borrow, rust_obj);
172        // If we get a mutable reference, there should be no other reference alive
173        if  matches!(guard.content(), BorrowState::Mutable(_)) || matches!(guard.content(), BorrowState::Immutable(_)) {
174            return Err(RustObjAccessError::BorrowConflict);
175        }
176        Ok(f())
177    }
178
179    pub fn get_rc(&self) -> Rc<RefCell<T>> {
180        self.shared_reference.clone()
181    }
182
183    fn contains(&self, obj: &T) -> bool {
184        let expected = self.shared_reference.as_ptr() as *const ();
185        let actual = obj as *const T as *const ();
186        expected == actual
187    }
188}
189
190
191enum BorrowState<T: ?Sized> {
192    None,
193    Immutable(*const T),
194    Mutable(*mut T),
195}
196
197impl<T: ?Sized> BorrowState<T> {
198    fn consume(cell: &Cell<Self>) -> BorrowGuard<'_, T> {
199        let consumed = cell.replace(BorrowState::None);
200        BorrowGuard { cell, consumed }
201    }
202
203    fn store<'a>(cell: &'a Cell<Self>, rust_obj: &T) -> BorrowGuard<'a, T> {
204        let old = cell.replace(BorrowState::Immutable(rust_obj as *const T));
205        BorrowGuard { cell, consumed: old }
206    }
207
208    fn store_mut<'a>(cell: &'a Cell<Self>, rust_obj: &mut T) -> BorrowGuard<'a, T> {
209        let old = cell.replace(BorrowState::Mutable(rust_obj as *mut T));
210        BorrowGuard { cell, consumed: old }
211    }
212}
213
214struct BorrowGuard<'a, T: ?Sized> {
215    cell: &'a Cell<BorrowState<T>>,
216    consumed: BorrowState<T>,
217}
218
219impl<T: ?Sized> BorrowGuard<'_, T> {
220    fn content(&self) -> &BorrowState<T> {
221        &self.consumed
222    }
223}
224
225impl<T: ?Sized> Drop for BorrowGuard<'_, T> {
226    fn drop(&mut self) {
227        self.cell.set(mem::replace(&mut self.consumed, BorrowState::None));
228    }
229}
230
231#[derive(Debug)]
232pub enum RustObjAccessError {
233    BorrowError(std::cell::BorrowError),
234    BorrowMutError(std::cell::BorrowMutError),
235    BorrowConflict,
236}