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::MaybeUninit;
5use std::ptr::{self, NonNull, addr_of_mut};
6use std::rc::{Rc, Weak};
7use std::cell::{BorrowError, BorrowMutError, Cell, Ref, RefCell, RefMut};
8
9#[macro_export]
10macro_rules! call_rust_trait_impl {
11    // Mutable version. The mut in front of the self is just a marker
12    (mut $self:expr, $method:ident ( $($arg:expr),* )) => {
13        $self.rust_obj
14            .try_with_borrow_mut(|vtable| {
15                vtable.$method($($arg),*)
16            })
17            .expect(concat!(
18                "Failed to borrow mutably for ",
19                stringify!($method),
20                "()"
21            ))
22    };
23
24    // Immutable version. Without the mut marker before self
25    ($self:expr, $method:ident ( $($arg:expr),* )) => {
26        $self.rust_obj
27            .try_with_borrow(|vtable| {
28                vtable.$method($($arg),*)
29            })
30            .expect(concat!(
31                "Failed to borrow for ",
32                stringify!($method),
33                "()"
34            ))
35    };
36}
37
38#[macro_export]
39macro_rules! call_cpp_impl {
40    (mut $self:expr, $method:ident ( $($arg:expr),* )) => {{
41        let proxy = unsafe {
42            $self.cpp_proxy
43                .as_mut()
44                .expect("cpp_proxy was null")
45        };
46        let proxy_pinned = unsafe { std::pin::Pin::new_unchecked(proxy) };
47        $self.rust_obj
48            .try_store_handle_and_call_qml_mut(|_| proxy_pinned.$method($($arg),*))
49            .expect(concat!(
50                "Failed to borrow mutably for ",
51                stringify!($method),
52                "()"
53            ))
54    }};
55
56    ($self:expr, $method:ident ( $($arg:expr),* )) => {{
57        let proxy = unsafe {
58            $self.cpp_proxy
59                .as_ref()
60                .expect("cpp_proxy was null")
61        };
62        $self.rust_obj
63            .try_store_handle_and_call_qml(|_| proxy.$method($($arg),*))
64            .expect(concat!(
65                "Failed to borrow for ",
66                stringify!($method),
67                "()"
68            ))
69    }};
70}
71
72
73/// A structure that provides controlled access to a Rust object by invoking functors
74/// (potentially recursively) on the object while it is borrowed immutably or mutably.
75///
76/// Supports recursive borrowing under the following rules:
77/// * If the object is initially borrowed mutably, both mutable and immutable recursive
78///   calls are allowed.
79/// * If the object is initially borrowed immutably, only immutable recursive calls are allowed.
80///
81/// The object is held using `Rc<RefCell<T>>` or `Weak<RefCell<T>>`.
82///
83/// The need for recursive borrowing (even mutable) appears from the separation
84/// between Rust struct and C++ class we have to live with. The group of the objects consists of:
85/// * the Rust object itself.
86/// * the C++ proxy implementing needed C++ interface, forwarding calls to the Rust Proxy.
87/// * the Rust proxy that connects C++ proxy and Rust object via 'CXX'.
88/// In pure C++ implementation that would typically be a single class inheriting from a needed base class / interface.
89/// However, because Rust lacks inheritance, we have to deal with Rust/C++ objects Frankenstein.
90///
91/// In summary, this struct prevents borrow errors and panics in scenarios involving
92/// recursive call chains such as:
93///     Rust object -> Base implementation in C++ proxy -> Rust object
94/// even though this requires bending standard Rust borrowing principles.
95/// From the user’s perspective, borrowing semantics remain equivalent to working with a
96/// normal `Rc<RefCell<T>>`.
97pub struct RustObjAccess<T: ?Sized + 'static>
98{
99    shared_reference: SharedReferenceWithQml<T>,
100    borrow_handle: Cell<*mut RustObjBorrowHandle<'static, T>>,
101}
102
103impl<T: ?Sized> RustObjAccess<T> {
104    pub fn new_strong(ptr: Rc<RefCell<T>>) -> Self {
105        Self {
106            shared_reference: SharedReferenceWithQml::OwnedByRust(ptr),
107            borrow_handle: Cell::new(ptr::null_mut()),
108        }
109    }
110
111    pub fn new_weak(ptr: Weak<RefCell<T>>) -> Self {
112        Self {
113            shared_reference: SharedReferenceWithQml::OwnedByQml(ptr),
114            borrow_handle: Cell::new(ptr::null_mut()),
115        }
116    }
117
118    pub fn try_with_borrow<F, R>(&self, f: F) -> Result<R, RustObjAccessError>
119    where F:FnOnce(&T) -> R
120    {
121        // We already borrowed and store a reference and can execute on it
122        let ptr_to_borrowed = self.borrow_handle.get();
123        if let Some(borrowed) = unsafe { ptr_to_borrowed.as_ref() } {
124            return Ok(f(borrowed.obj_ref.deref()))
125        };
126
127        // Create struct holding a reference on the stack
128        let rc = self.shared_reference.get_rc()
129            .ok_or(RustObjAccessError::ExpiredWeakPtr)?;
130        let mut borrowed = RustObjBorrowHandle::new(rc, false)?;
131
132        // Write the pointer to this object on the stack to self.borrowed
133        self.borrow_handle.set(&mut borrowed);
134        let result = f(borrowed.obj_ref.deref());
135        self.borrow_handle.set(ptr::null_mut());
136
137        Ok(result)
138    }
139
140    pub fn try_with_borrow_mut<F, R>(&self, f: F) -> Result<R, RustObjAccessError>
141    where F:FnOnce(&mut T) -> R
142    {
143        let ptr_to_borrowed = self.borrow_handle.get();
144        if let Some(borrowed) = unsafe { ptr_to_borrowed.as_mut() } {
145            match &mut borrowed.obj_ref {
146                RustHandle::Immutable(_) |
147                RustHandle::ImmutableUnguardedBorrow(_) => {
148                    // Call try_borrow_mut() to get BorrowMutError and return it from the function
149                    let _ref = borrowed.obj_rc.try_borrow_mut()
150                        .map_err(|err| RustObjAccessError::BorrowMutError(err))?;
151                    unreachable!()
152                },
153                RustHandle::Mutable(_) |
154                RustHandle::MutableUnguardedBorrow(_) => {
155                    return Ok(f(borrowed.obj_ref.deref_mut().unwrap()))
156                }
157            }
158        }
159
160        let rc = self.shared_reference.get_rc()
161            .ok_or(RustObjAccessError::ExpiredWeakPtr)?;
162        let mut borrowed = RustObjBorrowHandle::new(rc, true)?;
163
164        self.borrow_handle.set(&mut borrowed);
165        let result = f(borrowed.obj_ref.deref_mut().unwrap());
166        self.borrow_handle.set(ptr::null_mut());
167
168        Ok(result)
169    }
170
171    /// Executes `f` on the inner `T` assuming that a valid `RefCell` borrow
172    /// already exists somewhere in the current call stack.
173    ///
174    /// This is intended for Rust -> C++/QML -> Rust re-entry scenarios:
175    /// Rust code borrows from `Rc<RefCell<T>>` and calls into C++/QML code.
176    /// That code may call back into Rust. Borrowing the `RefCell` again when
177    /// re-entering Rust would normally fail, because the original borrow is
178    /// still active.
179    ///
180    /// To support this case, the function stores access to the object and
181    /// invokes `f` using a raw pointer obtained via `Rc::as_ptr()`, bypassing
182    /// the `RefCell` borrow mechanism.
183    ///
184    /// This must only be used when the caller can guarantee that a valid
185    /// borrow of the `RefCell` already exists externally. Rust code that
186    /// directly accesses the object should use the normal `borrow()` or
187    /// `borrow_mut()` APIs instead.
188    ///
189    /// The function performs runtime checks to validate the assumption that
190    /// the borrow originates outside this call.
191    pub fn try_store_handle_and_call_qml<F, R>(&self, f: F) -> Result<R, RustObjAccessError>
192    where F:FnOnce(&T) -> R
193    {
194        let ptr_to_borrowed = self.borrow_handle.get();
195        if let Some(borrowed) = unsafe { ptr_to_borrowed.as_ref() } {
196            return Ok(f(borrowed.obj_ref.deref()))
197        }
198
199        let rc = self.shared_reference.get_rc()
200            .ok_or(RustObjAccessError::ExpiredWeakPtr)?;
201
202        // Check that object is actually borrowed immutably at the moment.
203        // TODO: add #[cfg(debug_assertions)] if these checks as slow.
204        {
205            // If try_borrow_mut() succeeds - the object is not borrowed.
206            match rc.try_borrow_mut() {
207                Ok(_) => return Err(RustObjAccessError::ExpectedBorrowed),
208                Err(_) => {},
209            }
210
211            // If try_borrow() fails - it means that the object is already borrowed mutably.
212            rc.try_borrow()
213                .map_err(|_err| RustObjAccessError::ExpectedBorrowed)?;
214        }
215
216        let mut borrowed = RustObjBorrowHandle::new_unguarded(rc, false);
217
218        self.borrow_handle.set(&mut borrowed);
219        let result = f(borrowed.obj_ref.deref());
220        self.borrow_handle.set(ptr::null_mut());
221
222        Ok(result)
223    }
224
225    /// Similar to [`try_store_handle_and_call_qml`].
226    pub fn try_store_handle_and_call_qml_mut<F, R>(&self, f: F) -> Result<R, RustObjAccessError>
227    where F:FnOnce(&mut T) -> R
228    {
229        let ptr_to_borrowed = self.borrow_handle.get();
230        if let Some(borrowed) = unsafe { ptr_to_borrowed.as_mut() } {
231             match &mut borrowed.obj_ref {
232                RustHandle::Immutable(_) |
233                RustHandle::ImmutableUnguardedBorrow(_) => {
234                    // Call try_borrow_mut() to get BorrowMutError and return it from the function
235                    let _ref = borrowed.obj_rc.try_borrow_mut()
236                        .map_err(|err| RustObjAccessError::BorrowMutError(err))?;
237                    panic!("Object assumed to be borrowed but it is not")
238                },
239                RustHandle::Mutable(_) |
240                RustHandle::MutableUnguardedBorrow(_) => {
241                    return Ok(f(borrowed.obj_ref.deref_mut().unwrap()))
242                }
243            }
244        }
245
246        let rc = self.shared_reference.get_rc()
247            .ok_or(RustObjAccessError::ExpiredWeakPtr)?;
248
249        // Check that object is actually borrowed mutably
250        // TODO: add #[cfg(debug_assertions)] if these checks as slow.
251        {
252            // If try_borrow_mut() succeeds - then object is not borrowed.
253            match rc.try_borrow_mut() {
254                Ok(_) => return Err(RustObjAccessError::ExpectedBorrowedMut),
255                Err(_) => {},
256            }
257
258            // If try_borrow() succeeds - then object is borrowed but immutably.
259            match rc.try_borrow() {
260                Ok(_) => return Err(RustObjAccessError::ExpectedBorrowedMut),
261                Err(_) => {},
262            }
263        }
264
265        let mut borrowed = RustObjBorrowHandle::new_unguarded(rc, true);
266
267        self.borrow_handle.set(&mut borrowed);
268        let result = f(borrowed.obj_ref.deref_mut().unwrap());
269        self.borrow_handle.set(ptr::null_mut());
270
271        Ok(result)
272    }
273
274}
275
276/// Enum containing possible errors that may occur on attempting to borrow object.
277#[derive(Debug)]
278pub enum RustObjAccessError {
279    /// Standard error returned from RefCell::try_borrow()
280    BorrowError(BorrowError),
281    BorrowMutError(BorrowMutError),
282    ExpiredWeakPtr,
283    ExpectedBorrowed,
284    ExpectedBorrowedMut,
285}
286
287impl From<BorrowError> for RustObjAccessError {
288    fn from(value: BorrowError) -> Self {
289        Self::BorrowError(value)
290    }
291}
292
293impl From<BorrowMutError> for RustObjAccessError {
294    fn from(value: BorrowMutError) -> Self {
295        Self::BorrowMutError(value)
296    }
297}
298
299pub enum SharedReferenceWithQml<T: ?Sized> {
300    OwnedByRust(Rc<RefCell<T>>),
301    OwnedByQml(Weak<RefCell<T>>),
302}
303
304impl<T: ?Sized> SharedReferenceWithQml<T> {
305    fn get_rc(&self) -> Option<Rc<RefCell<T>>> {
306        match self {
307            SharedReferenceWithQml::OwnedByRust(rc) => Some(rc.clone()),
308            SharedReferenceWithQml::OwnedByQml(weak) => weak.upgrade()
309        }
310    }
311}
312
313/// Struct with a borrowed object
314struct RustObjBorrowHandle<'a, T: ?Sized> {
315    /// Strong pointer being held to make sure referenced object is alive while borrowed.
316    obj_rc: Rc<RefCell<T>>,
317
318    /// Ref or RefMut obtained by borrowing from RefCell.
319    obj_ref: RustHandle<'a, T>,
320}
321
322impl<'a, T: ?Sized> RustObjBorrowHandle<'a, T> {
323    fn new(rc: Rc<RefCell<T>>, is_mutable: bool) -> Result<Self, RustObjAccessError> {
324        let mut uninit: MaybeUninit<Self> = MaybeUninit::uninit();
325        let ptr = uninit.as_mut_ptr();
326        unsafe {
327            addr_of_mut!((*ptr).obj_rc).write(rc.clone());
328            addr_of_mut!((*ptr).obj_ref).write(RustHandle::new(&(*ptr).obj_rc, is_mutable)?);
329            Ok(uninit.assume_init())
330        }
331    }
332
333    fn new_unguarded(rc: Rc<RefCell<T>>, is_mutable: bool) -> Self {
334        let mut uninit: MaybeUninit<Self> = MaybeUninit::uninit();
335        let ptr = uninit.as_mut_ptr();
336        unsafe {
337            addr_of_mut!((*ptr).obj_rc).write(rc.clone());
338            addr_of_mut!((*ptr).obj_ref).write(RustHandle::new_unguarded(&(*ptr).obj_rc, is_mutable));
339            uninit.assume_init()
340        }
341    }
342}
343
344/// Mutable or immutable reference to Rust object
345/// obtained via RefCell::try_borrow()/try_borrow_mut()
346/// or RefCell::as_ptr() if the object was already borrowed externally
347enum RustHandle<'a, T: ?Sized> {
348    Immutable(Ref<'a, T>),
349    Mutable(RefMut<'a, T>),
350    ImmutableUnguardedBorrow(NonNull<T>),
351    MutableUnguardedBorrow(NonNull<T>),
352}
353
354impl<'a, T: ?Sized> RustHandle<'a, T> {
355    fn new(rc: &'a Rc<RefCell<T>>, is_mutable: bool) -> Result<Self, RustObjAccessError> {
356        match is_mutable {
357            true => Ok(Self::Mutable(
358                rc.try_borrow_mut()
359                    .map_err(RustObjAccessError::from)?
360            )),
361            false => Ok(Self::Immutable(
362                rc.try_borrow()
363                    .map_err(RustObjAccessError::from)?
364            ))
365        }
366    }
367
368    fn new_unguarded(rc: &'a Rc<RefCell<T>>, is_mutable: bool) -> Self {
369        // This Rc is stored in parent struct
370        // so there is no risk of input rc getting out of scope.
371        let nn = NonNull::new(rc.as_ptr()).unwrap();
372        match is_mutable {
373            true => Self::MutableUnguardedBorrow(nn),
374            false => Self::ImmutableUnguardedBorrow(nn),
375        }
376    }
377
378    fn deref(&self) -> &T {
379        match self {
380            Self::Immutable(ref_) => ref_,
381            Self::Mutable(ref_mut) => ref_mut,
382            Self::ImmutableUnguardedBorrow(ptr) => unsafe { ptr.as_ref() },
383            Self::MutableUnguardedBorrow(ptr) => unsafe {ptr.as_ref() },
384        }
385    }
386
387    fn deref_mut(&mut self) -> Option<&mut T> {
388        match self {
389            Self::Immutable(_) => None,
390            Self::ImmutableUnguardedBorrow(_) => None,
391            Self::Mutable(ref_mut) => Some(ref_mut),
392            Self::MutableUnguardedBorrow(ptr) => Some(unsafe {ptr.as_mut() }),
393        }
394    }
395}