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