[PATCH v2 04/16] rust/qobject: add Serializer (to_qobject) implementation

Paolo Bonzini
2026-01-08T13:13:28Z

This allows creating QObject from any serializable data structure.

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>
---
rust/util/meson.build | 2 +
rust/util/src/qobject/error.rs | 52 +++
rust/util/src/qobject/mod.rs | 4 +
rust/util/src/qobject/serializer.rs | 585 ++++++++++++++++++++++++++++
4 files changed, 643 insertions(+)
create mode 100644 rust/util/src/qobject/error.rs
create mode 100644 rust/util/src/qobject/serializer.rs

diff --git a/rust/util/meson.build b/rust/util/meson.build
index 166a792bce7..7829364c09a 100644
--- a/rust/util/meson.build
+++ b/rust/util/meson.build
@@ -40,6 +40,8 @@ _util_rs = static_library(
{'.': _util_bindings_inc_rs,
'qobject': [
'src/qobject/mod.rs',
+ 'src/qobject/error.rs',
+ 'src/qobject/serializer.rs',
'src/qobject/serialize.rs',
]}),
dependencies: [anyhow_rs, libc_rs, foreign_rs, glib_sys_rs, common_rs, serde_rs],
diff --git a/rust/util/src/qobject/error.rs b/rust/util/src/qobject/error.rs
new file mode 100644
index 00000000000..5212e65c4f7
--- /dev/null
+++ b/rust/util/src/qobject/error.rs
@@ -0,0 +1,52 @@
+//! Error data type for `QObject`'s `serde` integration
+
+use std::{
+ ffi::NulError,
+ fmt::{self, Display},
+ str::Utf8Error,
+};
+
+use serde::ser;
+
+#[derive(Debug)]
+pub enum Error {
+ Custom(String),
+ KeyMustBeAString,
+ InvalidUtf8,
+ NulEncountered,
+ NumberOutOfRange,
+}
+
+impl ser::Error for Error {
+ fn custom<T: Display>(msg: T) -> Self {
+ Error::Custom(msg.to_string())
+ }
+}
+
+impl From<NulError> for Error {
+ fn from(_: NulError) -> Self {
+ Error::NulEncountered
+ }
+}
+
+impl From<Utf8Error> for Error {
+ fn from(_: Utf8Error) -> Self {
+ Error::InvalidUtf8
+ }
+}
+
+impl Display for Error {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Error::Custom(msg) => formatter.write_str(msg),
+ Error::KeyMustBeAString => formatter.write_str("key must be a string"),
+ Error::InvalidUtf8 => formatter.write_str("invalid UTF-8 in string"),
+ Error::NulEncountered => formatter.write_str("NUL character in string"),
+ Error::NumberOutOfRange => formatter.write_str("number out of range"),
+ }
+ }
+}
+
+impl std::error::Error for Error {}
+
+pub type Result<T> = std::result::Result<T, Error>;
diff --git a/rust/util/src/qobject/mod.rs b/rust/util/src/qobject/mod.rs
index 7b9f0516fe1..bfa3e9e34bb 100644
--- a/rust/util/src/qobject/mod.rs
+++ b/rust/util/src/qobject/mod.rs
@@ -6,7 +6,9 @@

#![deny(clippy::unwrap_used)]

+mod error;
mod serialize;
+mod serializer;

use std::{
cell::UnsafeCell,
@@ -17,6 +19,8 @@
};

use common::assert_field_type;
+pub use error::{Error, Result};
+pub use serializer::to_qobject;

use crate::bindings;

diff --git a/rust/util/src/qobject/serializer.rs b/rust/util/src/qobject/serializer.rs
new file mode 100644
index 00000000000..08730855731
--- /dev/null
+++ b/rust/util/src/qobject/serializer.rs
@@ -0,0 +1,585 @@
+//! `QObject` serializer
+//!
+//! This module implements a [`Serializer`](serde::ser::Serializer) that
+//! produces `QObject`s, allowing them to be created from serializable data
+//! structures (such as primitive data types, or structs that implement
+//! `Serialize`).
+
+use std::ffi::CString;
+
+use serde::ser::{Impossible, Serialize};
+
+use super::{
+ error::{Error, Result},
+ QObject,
+};
+
+pub struct SerializeTupleVariant {
+ name: CString,
+ vec: Vec<QObject>,
+}
+
+impl serde::ser::SerializeTupleVariant for SerializeTupleVariant {
+ type Ok = QObject;
+ type Error = Error;
+
+ fn serialize_field<T>(&mut self, value: &T) -> Result<()>
+ where
+ T: ?Sized + Serialize,
+ {
+ self.vec.push(to_qobject(value)?);
+ Ok(())
+ }
+
+ fn end(self) -> Result<QObject> {
+ let SerializeTupleVariant { name, vec, .. } = self;
+
+ // TODO: insert elements one at a time
+ let list = QObject::from_iter(vec);
+
+ // serde by default represents enums as a single-entry object, with
+ // the variant stored in the key ("external tagging"). Internal tagging
+ // is implemented by the struct that requests it, not by the serializer.
+ let map = [(name, list)];
+ Ok(QObject::from_iter(map))
+ }
+}
+
+pub struct SerializeStructVariant {
+ name: CString,
+ vec: Vec<(CString, QObject)>,
+}
+
+impl serde::ser::SerializeStructVariant for SerializeStructVariant {
+ type Ok = QObject;
+ type Error = Error;
+
+ fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<()>
+ where
+ T: ?Sized + Serialize,
+ {
+ self.vec.push((CString::new(key)?, to_qobject(value)?));
+ Ok(())
+ }
+
+ fn end(self) -> Result<QObject> {
+ // TODO: insert keys one at a time
+ let SerializeStructVariant { name, vec, .. } = self;
+ let list = QObject::from_iter(vec);
+
+ // serde by default represents enums as a single-entry object, with
+ // the variant stored in the key ("external tagging"). Internal tagging
+ // is implemented by the struct that requests it, not by the serializer.
+ let map = [(name, list)];
+ Ok(QObject::from_iter(map))
+ }
+}
+
+pub struct SerializeVec {
+ vec: Vec<QObject>,
+}
+
+impl serde::ser::SerializeSeq for SerializeVec {
+ type Ok = QObject;
+ type Error = Error;
+
+ fn serialize_element<T>(&mut self, value: &T) -> Result<()>
+ where
+ T: ?Sized + Serialize,
+ {
+ self.vec.push(to_qobject(value)?);
+ Ok(())
+ }
+
+ fn end(self) -> Result<QObject> {
+ // TODO: insert elements one at a time
+ let SerializeVec { vec, .. } = self;
+ let list = QObject::from_iter(vec);
+ Ok(list)
+ }
+}
+
+impl serde::ser::SerializeTuple for SerializeVec {
+ type Ok = QObject;
+ type Error = Error;
+
+ fn serialize_element<T>(&mut self, value: &T) -> Result<()>
+ where
+ T: ?Sized + Serialize,
+ {
+ serde::ser::SerializeSeq::serialize_element(self, value)
+ }
+
+ fn end(self) -> Result<QObject> {
+ serde::ser::SerializeSeq::end(self)
+ }
+}
+
+impl serde::ser::SerializeTupleStruct for SerializeVec {
+ type Ok = QObject;
+ type Error = Error;
+
+ fn serialize_field<T>(&mut self, value: &T) -> Result<()>
+ where
+ T: ?Sized + Serialize,
+ {
+ serde::ser::SerializeSeq::serialize_element(self, value)
+ }
+
+ fn end(self) -> Result<QObject> {
+ serde::ser::SerializeSeq::end(self)
+ }
+}
+
+struct MapKeySerializer;
+
+impl serde::Serializer for MapKeySerializer {
+ type Ok = CString;
+ type Error = Error;
+
+ type SerializeSeq = Impossible<CString, Error>;
+ type SerializeTuple = Impossible<CString, Error>;
+ type SerializeTupleStruct = Impossible<CString, Error>;
+ type SerializeTupleVariant = Impossible<CString, Error>;
+ type SerializeMap = Impossible<CString, Error>;
+ type SerializeStruct = Impossible<CString, Error>;
+ type SerializeStructVariant = Impossible<CString, Error>;
+
+ #[inline]
+ fn serialize_unit_variant(
+ self,
+ _name: &'static str,
+ _variant_index: u32,
+ variant: &'static str,
+ ) -> Result<CString> {
+ Ok(CString::new(variant)?)
+ }
+
+ #[inline]
+ fn serialize_newtype_struct<T>(self, _name: &'static str, value: &T) -> Result<CString>
+ where
+ T: ?Sized + Serialize,
+ {
+ value.serialize(self)
+ }
+
+ fn serialize_bool(self, _value: bool) -> Result<CString> {
+ Err(Error::KeyMustBeAString)
+ }
+
+ fn serialize_i8(self, _value: i8) -> Result<CString> {
+ Err(Error::KeyMustBeAString)
+ }
+
+ fn serialize_i16(self, _value: i16) -> Result<CString> {
+ Err(Error::KeyMustBeAString)
+ }
+
+ fn serialize_i32(self, _value: i32) -> Result<CString> {
+ Err(Error::KeyMustBeAString)
+ }
+
+ fn serialize_i64(self, _value: i64) -> Result<CString> {
+ Err(Error::KeyMustBeAString)
+ }
+
+ fn serialize_i128(self, _value: i128) -> Result<CString> {
+ Err(Error::KeyMustBeAString)
+ }
+
+ fn serialize_u8(self, _value: u8) -> Result<CString> {
+ Err(Error::KeyMustBeAString)
+ }
+
+ fn serialize_u16(self, _value: u16) -> Result<CString> {
+ Err(Error::KeyMustBeAString)
+ }
+
+ fn serialize_u32(self, _value: u32) -> Result<CString> {
+ Err(Error::KeyMustBeAString)
+ }
+
+ fn serialize_u64(self, _value: u64) -> Result<CString> {
+ Err(Error::KeyMustBeAString)
+ }
+
+ fn serialize_u128(self, _value: u128) -> Result<CString> {
+ Err(Error::KeyMustBeAString)
+ }
+
+ fn serialize_f32(self, _value: f32) -> Result<CString> {
+ Err(Error::KeyMustBeAString)
+ }
+
+ fn serialize_f64(self, _value: f64) -> Result<CString> {
+ Err(Error::KeyMustBeAString)
+ }
+
+ #[inline]
+ fn serialize_char(self, _value: char) -> Result<CString> {
+ Err(Error::KeyMustBeAString)
+ }
+
+ #[inline]
+ fn serialize_str(self, value: &str) -> Result<CString> {
+ Ok(CString::new(value)?)
+ }
+
+ fn serialize_bytes(self, value: &[u8]) -> Result<CString> {
+ Ok(CString::new(value)?)
+ }
+
+ fn serialize_unit(self) -> Result<CString> {
+ Err(Error::KeyMustBeAString)
+ }
+
+ fn serialize_unit_struct(self, _name: &'static str) -> Result<CString> {
+ Err(Error::KeyMustBeAString)
+ }
+
+ fn serialize_newtype_variant<T>(
+ self,
+ _name: &'static str,
+ _variant_index: u32,
+ _variant: &'static str,
+ _value: &T,
+ ) -> Result<CString>
+ where
+ T: ?Sized + Serialize,
+ {
+ Err(Error::KeyMustBeAString)
+ }
+
+ fn serialize_none(self) -> Result<CString> {
+ Err(Error::KeyMustBeAString)
+ }
+
+ fn serialize_some<T>(self, _value: &T) -> Result<CString>
+ where
+ T: ?Sized + Serialize,
+ {
+ Err(Error::KeyMustBeAString)
+ }
+
+ fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq> {
+ Err(Error::KeyMustBeAString)
+ }
+
+ fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple> {
+ Err(Error::KeyMustBeAString)
+ }
+
+ fn serialize_tuple_struct(
+ self,
+ _name: &'static str,
+ _len: usize,
+ ) -> Result<Self::SerializeTupleStruct> {
+ Err(Error::KeyMustBeAString)
+ }
+
+ fn serialize_tuple_variant(
+ self,
+ _name: &'static str,
+ _variant_index: u32,
+ _variant: &'static str,
+ _len: usize,
+ ) -> Result<Self::SerializeTupleVariant> {
+ Err(Error::KeyMustBeAString)
+ }
+
+ fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> {
+ Err(Error::KeyMustBeAString)
+ }
+
+ fn serialize_struct(self, _name: &'static str, _len: usize) -> Result<Self::SerializeStruct> {
+ Err(Error::KeyMustBeAString)
+ }
+
+ fn serialize_struct_variant(
+ self,
+ _name: &'static str,
+ _variant_index: u32,
+ _variant: &'static str,
+ _len: usize,
+ ) -> Result<Self::SerializeStructVariant> {
+ Err(Error::KeyMustBeAString)
+ }
+}
+
+pub struct SerializeMap {
+ vec: Vec<(CString, QObject)>,
+ next_key: Option<CString>,
+}
+
+impl serde::ser::SerializeMap for SerializeMap {
+ type Ok = QObject;
+ type Error = Error;
+
+ fn serialize_key<T>(&mut self, key: &T) -> Result<()>
+ where
+ T: ?Sized + Serialize,
+ {
+ self.next_key = Some(key.serialize(MapKeySerializer)?);
+ Ok(())
+ }
+
+ fn serialize_value<T>(&mut self, value: &T) -> Result<()>
+ where
+ T: ?Sized + Serialize,
+ {
+ let key = self.next_key.take();
+ // Panic because this indicates a bug in the program rather than an
+ // expected failure.
+ let key = key.expect("serialize_value called before serialize_key");
+ self.vec.push((key, to_qobject(value)?));
+ Ok(())
+ }
+
+ fn end(self) -> Result<QObject> {
+ // TODO: insert keys one at a time
+ let SerializeMap { vec, .. } = self;
+ Ok(QObject::from_iter(vec))
+ }
+}
+
+impl serde::ser::SerializeStruct for SerializeMap {
+ type Ok = QObject;
+ type Error = Error;
+
+ fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<()>
+ where
+ T: ?Sized + Serialize,
+ {
+ serde::ser::SerializeMap::serialize_entry(self, key, value)
+ }
+
+ fn end(self) -> Result<QObject> {
+ serde::ser::SerializeMap::end(self)
+ }
+}
+
+/// Serializer whose output is a `QObject`.
+///
+/// This is the serializer that backs [`to_qobject`].
+pub struct Serializer;
+
+impl serde::Serializer for Serializer {
+ type Ok = QObject;
+ type Error = Error;
+ type SerializeSeq = SerializeVec;
+ type SerializeTuple = SerializeVec;
+ type SerializeTupleStruct = SerializeVec;
+ type SerializeTupleVariant = SerializeTupleVariant;
+ type SerializeMap = SerializeMap;
+ type SerializeStruct = SerializeMap;
+ type SerializeStructVariant = SerializeStructVariant;
+
+ #[inline]
+ fn serialize_bool(self, value: bool) -> Result<QObject> {
+ Ok(value.into())
+ }
+
+ #[inline]
+ fn serialize_i8(self, value: i8) -> Result<QObject> {
+ Ok(value.into())
+ }
+
+ #[inline]
+ fn serialize_i16(self, value: i16) -> Result<QObject> {
+ Ok(value.into())
+ }
+
+ #[inline]
+ fn serialize_i32(self, value: i32) -> Result<QObject> {
+ Ok(value.into())
+ }
+
+ fn serialize_i64(self, value: i64) -> Result<QObject> {
+ Ok(value.into())
+ }
+
+ fn serialize_i128(self, value: i128) -> Result<QObject> {
+ if let Ok(value) = u64::try_from(value) {
+ Ok(value.into())
+ } else if let Ok(value) = i64::try_from(value) {
+ Ok(value.into())
+ } else {
+ Err(Error::NumberOutOfRange)
+ }
+ }
+
+ #[inline]
+ fn serialize_u8(self, value: u8) -> Result<QObject> {
+ Ok(value.into())
+ }
+
+ #[inline]
+ fn serialize_u16(self, value: u16) -> Result<QObject> {
+ Ok(value.into())
+ }
+
+ #[inline]
+ fn serialize_u32(self, value: u32) -> Result<QObject> {
+ Ok(value.into())
+ }
+
+ #[inline]
+ fn serialize_u64(self, value: u64) -> Result<QObject> {
+ Ok(value.into())
+ }
+
+ fn serialize_u128(self, value: u128) -> Result<QObject> {
+ if let Ok(value) = u64::try_from(value) {
+ Ok(value.into())
+ } else {
+ Err(Error::NumberOutOfRange)
+ }
+ }
+
+ #[inline]
+ fn serialize_f32(self, float: f32) -> Result<QObject> {
+ Ok(float.into())
+ }
+
+ #[inline]
+ fn serialize_f64(self, float: f64) -> Result<QObject> {
+ Ok(float.into())
+ }
+
+ #[inline]
+ fn serialize_char(self, value: char) -> Result<QObject> {
+ let mut s = String::new();
+ s.push(value);
+ Ok(CString::new(s)?.into())
+ }
+
+ #[inline]
+ fn serialize_str(self, value: &str) -> Result<QObject> {
+ Ok(CString::new(value)?.into())
+ }
+
+ fn serialize_bytes(self, value: &[u8]) -> Result<QObject> {
+ // Serialize into a vector of numeric QObjects
+ let it = value.iter().copied();
+ Ok(QObject::from_iter(it))
+ }
+
+ #[inline]
+ fn serialize_unit(self) -> Result<QObject> {
+ Ok(().into())
+ }
+
+ #[inline]
+ fn serialize_unit_struct(self, _name: &'static str) -> Result<QObject> {
+ self.serialize_unit()
+ }
+
+ #[inline]
+ fn serialize_unit_variant(
+ self,
+ _name: &'static str,
+ _variant_index: u32,
+ variant: &'static str,
+ ) -> Result<QObject> {
+ self.serialize_str(variant)
+ }
+
+ #[inline]
+ fn serialize_newtype_struct<T>(self, _name: &'static str, value: &T) -> Result<QObject>
+ where
+ T: ?Sized + Serialize,
+ {
+ value.serialize(self)
+ }
+
+ fn serialize_newtype_variant<T>(
+ self,
+ _name: &'static str,
+ _variant_index: u32,
+ variant: &'static str,
+ value: &T,
+ ) -> Result<QObject>
+ where
+ T: ?Sized + Serialize,
+ {
+ // serde by default represents enums as a single-entry object, with
+ // the variant stored in the key ("external tagging"). Internal tagging
+ // is implemented by the struct that requests it, not by the serializer.
+ let map = [(CString::new(variant)?, to_qobject(value)?)];
+ Ok(QObject::from_iter(map))
+ }
+
+ #[inline]
+ fn serialize_none(self) -> Result<QObject> {
+ self.serialize_unit()
+ }
+
+ #[inline]
+ fn serialize_some<T>(self, value: &T) -> Result<QObject>
+ where
+ T: ?Sized + Serialize,
+ {
+ value.serialize(self)
+ }
+
+ fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq> {
+ Ok(SerializeVec {
+ vec: Vec::with_capacity(len.unwrap_or(0)),
+ })
+ }
+
+ fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple> {
+ self.serialize_seq(Some(len))
+ }
+
+ fn serialize_tuple_struct(
+ self,
+ _name: &'static str,
+ len: usize,
+ ) -> Result<Self::SerializeTupleStruct> {
+ self.serialize_seq(Some(len))
+ }
+
+ fn serialize_tuple_variant(
+ self,
+ _name: &'static str,
+ _variant_index: u32,
+ variant: &'static str,
+ len: usize,
+ ) -> Result<Self::SerializeTupleVariant> {
+ Ok(SerializeTupleVariant {
+ name: CString::new(variant)?,
+ vec: Vec::with_capacity(len),
+ })
+ }
+
+ fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> {
+ Ok(SerializeMap {
+ vec: Vec::new(),
+ next_key: None,
+ })
+ }
+ fn serialize_struct(self, _name: &'static str, len: usize) -> Result<Self::SerializeStruct> {
+ self.serialize_map(Some(len))
+ }
+
+ fn serialize_struct_variant(
+ self,
+ _name: &'static str,
+ _variant_index: u32,
+ variant: &'static str,
+ _len: usize,
+ ) -> Result<Self::SerializeStructVariant> {
+ Ok(SerializeStructVariant {
+ name: CString::new(variant)?,
+ vec: Vec::new(),
+ })
+ }
+}
+
+pub fn to_qobject<T>(input: T) -> Result<QObject>
+where
+ T: Serialize,
+{
+ input.serialize(Serializer)
+}
--
2.52.0