[PATCH v2 01/16] rust/qobject: add basic bindings

Paolo Bonzini
2026-01-08T13:11:12Z

This is only a basic API, intended to be used by the serde traits.

Co-authored-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Zhao Liu <zhao1.liu@intel.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
include/qobject/qobject.h | 5 +-
rust/util/wrapper.h | 7 +
rust/util/meson.build | 6 +-
rust/util/src/lib.rs | 4 +
rust/util/src/qobject/mod.rs | 331 +++++++++++++++++++++++++++++++++++
5 files changed, 350 insertions(+), 3 deletions(-)
create mode 100644 rust/util/src/qobject/mod.rs

diff --git a/include/qobject/qobject.h b/include/qobject/qobject.h
index 02f4c6a6eb2..567da7b6c51 100644
--- a/include/qobject/qobject.h
+++ b/include/qobject/qobject.h
@@ -35,7 +35,10 @@
#include "qemu/atomic.h"
#include "qapi/qapi-builtin-types.h"

-/* Not for use outside include/qobject/ */
+/*
+ * Not for use outside include/qobject/ (and Rust bindings, when they
+ * have to redo inline functions here).
+ */
struct QObjectBase_ {
QType type;
size_t refcnt;
diff --git a/rust/util/wrapper.h b/rust/util/wrapper.h
index b9ed68a01d8..0907dd59142 100644
--- a/rust/util/wrapper.h
+++ b/rust/util/wrapper.h
@@ -30,3 +30,10 @@ typedef enum memory_order {
#include "qemu/log.h"
#include "qemu/module.h"
#include "qemu/timer.h"
+#include "qobject/qnull.h"
+#include "qobject/qbool.h"
+#include "qobject/qnum.h"
+#include "qobject/qstring.h"
+#include "qobject/qobject.h"
+#include "qobject/qlist.h"
+#include "qobject/qdict.h"
diff --git a/rust/util/meson.build b/rust/util/meson.build
index 98629394afb..28593286134 100644
--- a/rust/util/meson.build
+++ b/rust/util/meson.build
@@ -37,8 +37,10 @@ _util_rs = static_library(
'src/prelude.rs',
'src/timer.rs',
],
- {'.': _util_bindings_inc_rs}
- ),
+ {'.': _util_bindings_inc_rs,
+ 'qobject': [
+ 'src/qobject/mod.rs',
+ ]}),
dependencies: [anyhow_rs, libc_rs, foreign_rs, glib_sys_rs, common_rs],
)

diff --git a/rust/util/src/lib.rs b/rust/util/src/lib.rs
index 7d2de3ed811..96334466895 100644
--- a/rust/util/src/lib.rs
+++ b/rust/util/src/lib.rs
@@ -9,6 +9,10 @@
// for prelude-like modules
#[rustfmt::skip]
pub mod prelude;
+
+#[macro_use]
+pub mod qobject;
+
pub mod timer;

pub use error::{Error, Result, ResultExt};
diff --git a/rust/util/src/qobject/mod.rs b/rust/util/src/qobject/mod.rs
new file mode 100644
index 00000000000..5744870ad1c
--- /dev/null
+++ b/rust/util/src/qobject/mod.rs
@@ -0,0 +1,331 @@
+//! `QObject` bindings
+//!
+//! This module implements bindings for QEMU's `QObject` data structure.
+//! The bindings integrate with `serde`, which take the role of visitors
+//! in Rust code.
+
+#![deny(clippy::unwrap_used)]
+
+use std::{
+ cell::UnsafeCell,
+ ffi::{c_char, CString},
+ mem::ManuallyDrop,
+ ptr::{addr_of, addr_of_mut},
+ sync::atomic::{AtomicUsize, Ordering},
+};
+
+use common::assert_field_type;
+
+use crate::bindings;
+
+/// A wrapper for a C `QObject`.
+///
+/// Because `QObject` is not thread-safe, the safety of these bindings
+/// right now hinges on treating them as immutable. It is part of the
+/// contract with the `QObject` constructors that the Rust struct is
+/// only built after the contents are stable.
+///
+/// Only a bare bones API is public; production and consumption of `QObject`
+/// generally goes through `serde`.
+pub struct QObject(&'static UnsafeCell<bindings::QObject>);
+
+// SAFETY: the QObject API are not thread-safe other than reference counting;
+// but the Rust struct is only created once the contents are stable, and
+// therefore it obeys the aliased XOR mutable invariant.
+unsafe impl Send for QObject {}
+unsafe impl Sync for QObject {}
+
+// Since a QObject can be a floating-point value, and potentially a NaN,
+// do not implement Eq
+impl PartialEq for QObject {
+ fn eq(&self, other: &Self) -> bool {
+ unsafe { bindings::qobject_is_equal(self.0.get(), other.0.get()) }
+ }
+}
+
+impl QObject {
+ /// Construct a [`QObject`] from a C `QObjectBase` pointer.
+ /// The caller cedes its reference to the returned struct.
+ ///
+ /// # Safety
+ ///
+ /// The `QObjectBase` must not be changed from C code while
+ /// the Rust `QObject` lives
+ const unsafe fn from_base(p: *const bindings::QObjectBase_) -> Self {
+ QObject(unsafe { &*p.cast() })
+ }
+
+ /// Construct a [`QObject`] from a C `QObject` pointer.
+ /// The caller cedes its reference to the returned struct.
+ ///
+ /// # Safety
+ ///
+ /// The `QObject` must not be changed from C code while
+ /// the Rust `QObject` lives
+ pub const unsafe fn from_raw(p: *const bindings::QObject) -> Self {
+ QObject(unsafe { &*p.cast() })
+ }
+
+ /// Obtain a raw C pointer from a reference. `self` is consumed
+ /// without decreasing the reference count; therefore, the reference
+ /// is transferred to the `*mut bindings::QObject`.
+ pub fn into_raw(self) -> *mut bindings::QObject {
+ let src = ManuallyDrop::new(self);
+ src.0.get()
+ }
+
+ /// Construct a [`QObject`] from a C `QObjectBase` pointer.
+ /// The caller *does not* cede its reference to the returned struct.
+ ///
+ /// # Safety
+ ///
+ /// The `QObjectBase` must not be changed from C code while
+ /// the Rust `QObject` lives
+ unsafe fn cloned_from_base(p: *const bindings::QObjectBase_) -> Self {
+ let orig = unsafe { ManuallyDrop::new(QObject::from_base(p)) };
+ (*orig).clone()
+ }
+
+ /// Construct a [`QObject`] from a C `QObject` pointer.
+ /// The caller *does not* cede its reference to the returned struct.
+ ///
+ /// # Safety
+ ///
+ /// The `QObject` must not be changed from C code while
+ /// the Rust `QObject` lives
+ pub unsafe fn cloned_from_raw(p: *const bindings::QObject) -> Self {
+ let orig = unsafe { ManuallyDrop::new(QObject::from_raw(p)) };
+ (*orig).clone()
+ }
+
+ fn refcnt(&self) -> &AtomicUsize {
+ assert_field_type!(bindings::QObjectBase_, refcnt, usize);
+ let qobj = self.0.get();
+ unsafe { AtomicUsize::from_ptr(addr_of_mut!((*qobj).base.refcnt)) }
+ }
+}
+
+/// Rust equivalent of the C `QOBJECT` macro; for internal use only, because
+/// all access should go through `From` (which already returns [`QObject`]
+/// or serde.
+macro_rules! qobject {
+ ($qobj:expr) => {{
+ let qobj: &bindings::QObjectBase_ = &$qobj.base;
+ // SAFETY: this `let` guarantees that either $qobj is a reference
+ // (not a raw pointer), or we're in an outer unsafe block
+ unsafe { QObject::from_base(qobj) }
+ }};
+}
+
+impl From<()> for QObject {
+ fn from(_null: ()) -> Self {
+ // Conversion of the C inline `qnull` function
+ unsafe { QObject::cloned_from_base(addr_of!(bindings::qnull_.base)) }
+ }
+}
+
+impl<T> From<Option<T>> for QObject
+where
+ QObject: From<T>,
+{
+ fn from(o: Option<T>) -> Self {
+ o.map_or_else(|| ().into(), Into::into)
+ }
+}
+
+impl From<bool> for QObject {
+ fn from(b: bool) -> Self {
+ let qobj = unsafe { &*bindings::qbool_from_bool(b) };
+ qobject!(qobj)
+ }
+}
+
+macro_rules! impl_from_return_qnum_int {
+ ($t:ty) => {
+ impl From<$t> for QObject {
+ fn from(n: $t) -> Self {
+ let qobj = unsafe { &*bindings::qnum_from_int(n.into()) };
+ qobject!(qobj)
+ }
+ }
+ };
+}
+
+impl_from_return_qnum_int!(i8);
+impl_from_return_qnum_int!(i16);
+impl_from_return_qnum_int!(i32);
+impl_from_return_qnum_int!(i64);
+
+macro_rules! impl_from_return_qnum_uint {
+ ($t:ty) => {
+ impl From<$t> for QObject {
+ fn from(n: $t) -> Self {
+ let qobj = unsafe { &*bindings::qnum_from_uint(n.into()) };
+ qobject!(qobj)
+ }
+ }
+ };
+}
+
+impl_from_return_qnum_uint!(u8);
+impl_from_return_qnum_uint!(u16);
+impl_from_return_qnum_uint!(u32);
+impl_from_return_qnum_uint!(u64);
+
+macro_rules! impl_from_return_qnum_double {
+ ($t:ty) => {
+ impl From<$t> for QObject {
+ fn from(n: $t) -> Self {
+ let qobj = unsafe { &*bindings::qnum_from_double(n.into()) };
+ qobject!(qobj)
+ }
+ }
+ };
+}
+
+impl_from_return_qnum_double!(f32);
+impl_from_return_qnum_double!(f64);
+
+impl From<CString> for QObject {
+ fn from(s: CString) -> Self {
+ let qobj = unsafe { &*bindings::qstring_from_str(s.as_ptr()) };
+ qobject!(qobj)
+ }
+}
+
+impl FromIterator for QObject
+where
+ Self: From,
+{
+ fn from_iter<I: IntoIterator<Item = A>>(it: I) -> Self {
+ let qlist = unsafe { &mut *bindings::qlist_new() };
+ for elem in it {
+ let elem: QObject = elem.into();
+ let elem = elem.into_raw();
+ unsafe {
+ bindings::qlist_append_obj(qlist, elem);
+ }
+ }
+ qobject!(qlist)
+ }
+}
+
+impl FromIterator<(CString, A)> for QObject
+where
+ Self: From,
+{
+ fn from_iter<I: IntoIterator<Item = (CString, A)>>(it: I) -> Self {
+ let qdict = unsafe { &mut *bindings::qdict_new() };
+ for (key, val) in it {
+ let val: QObject = val.into();
+ let val = val.into_raw();
+ unsafe {
+ bindings::qdict_put_obj(qdict, key.as_ptr().cast::<c_char>(), val);
+ }
+ }
+ qobject!(qdict)
+ }
+}
+
+impl Clone for QObject {
+ fn clone(&self) -> Self {
+ self.refcnt().fetch_add(1, Ordering::Acquire);
+ QObject(self.0)
+ }
+}
+
+impl Drop for QObject {
+ fn drop(&mut self) {
+ if self.refcnt().fetch_sub(1, Ordering::Release) == 1 {
+ unsafe {
+ bindings::qobject_destroy(self.0.get());
+ }
+ }
+ }
+}
+
+#[allow(unused)]
+macro_rules! match_qobject {
+ (@internal ($qobj:expr) =>
+ $(() => $unit:expr,)?
+ $(bool($boolvar:tt) => $bool:expr,)?
+ $(i64($i64var:tt) => $i64:expr,)?
+ $(u64($u64var:tt) => $u64:expr,)?
+ $(f64($f64var:tt) => $f64:expr,)?
+ $(CStr($cstrvar:tt) => $cstr:expr,)?
+ $(QList($qlistvar:tt) => $qlist:expr,)?
+ $(QDict($qdictvar:tt) => $qdict:expr,)?
+ $(_ => $other:expr,)?
+ ) => {
+ loop {
+ let qobj_ = $qobj.0.get();
+ match unsafe { &* qobj_ }.base.type_ {
+ $($crate::bindings::QTYPE_QNULL => break $unit,)?
+ $($crate::bindings::QTYPE_QBOOL => break {
+ let qbool__: *mut $crate::bindings::QBool = qobj_.cast();
+ let $boolvar = unsafe { (&*qbool__).value };
+ $bool
+ },)?
+ $crate::bindings::QTYPE_QNUM => {
+ let qnum__: *mut $crate::bindings::QNum = qobj_.cast();
+ let qnum__ = unsafe { &*qnum__ };
+ match qnum__.kind {
+ $crate::bindings::QNUM_I64 |
+ $crate::bindings::QNUM_U64 |
+ $crate::bindings::QNUM_DOUBLE => {}
+ _ => {
+ panic!("unreachable");
+ }
+ }
+
+ match qnum__.kind {
+ $($crate::bindings::QNUM_I64 => break {
+ let $i64var = unsafe { qnum__.u.i64_ };
+ $i64
+ },)?
+ $($crate::bindings::QNUM_U64 => break {
+ let $u64var = unsafe { qnum__.u.u64_ };
+ $u64
+ },)?
+ $($crate::bindings::QNUM_DOUBLE => break {
+ let $f64var = unsafe { qnum__.u.dbl };
+ $f64
+ },)?
+ _ => {} // evaluate $other
+ }
+ },
+ $($crate::bindings::QTYPE_QSTRING => break {
+ let qstring__: *mut $crate::bindings::QString = qobj_.cast();
+ let $cstrvar = unsafe { ::core::ffi::CStr::from_ptr((&*qstring__).string) };
+ $cstr
+ },)?
+ $($crate::bindings::QTYPE_QLIST => break {
+ let qlist__: *mut $crate::bindings::QList = qobj_.cast();
+ let $qlistvar = unsafe { &*qlist__ };
+ $qlist
+ },)?
+ $($crate::bindings::QTYPE_QDICT => break {
+ let qdict__: *mut $crate::bindings::QDict = qobj_.cast();
+ let $qdictvar = unsafe { &*qdict__ };
+ $qdict
+ },)?
+ _ => ()
+ };
+ $(break $other;)?
+ #[allow(unreachable_code)]
+ {
+ panic!("unreachable");
+ }
+ }
+ };
+
+ // first cleanup the syntax a bit, checking that there's at least
+ // one pattern and always adding a trailing comma
+ (($qobj:expr) =>
+ $($type:tt$(($val:tt))? => $code:expr ),+ $(,)?) => {
+ match_qobject!(@internal ($qobj) =>
+ $($type $(($val))? => $code,)+)
+ };
+}
+#[allow(unused_imports)]
+use match_qobject;
--
2.52.0