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