qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/4] rust: improvements to errors
@ 2025-10-31 15:25 Paolo Bonzini
  2025-10-31 15:25 ` [PATCH 1/4] rust/util: add ensure macro Paolo Bonzini
                   ` (3 more replies)
  0 siblings, 4 replies; 9+ messages in thread
From: Paolo Bonzini @ 2025-10-31 15:25 UTC (permalink / raw)
  To: qemu-devel; +Cc: qemu-rust

The first three patches are extracted from the serde work; as to the
fourth, I was thinking about it for a while - it provides an extension
trait for Result that makes it easy to use &error_fatal.  The main
advantage is to make it clear how errors are handled.

Paolo

Paolo Bonzini (4):
  rust/util: add ensure macro
  rust/util: use anyhow's native chaining capabilities
  rust/util: replace Error::err_or_unit/err_or_else with
    Error::with_errp
  rust: pull error_fatal out of SysbusDeviceMethods::sysbus_realize

 rust/hw/char/pl011/src/device.rs |   4 +-
 rust/hw/core/src/sysbus.rs       |  13 +-
 rust/hw/timer/hpet/src/device.rs |  21 ++-
 rust/hw/timer/hpet/src/fw_cfg.rs |   7 +-
 rust/util/src/error.rs           | 272 ++++++++++++++++++-------------
 rust/util/src/lib.rs             |   2 +-
 6 files changed, 183 insertions(+), 136 deletions(-)

-- 
2.51.1



^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH 1/4] rust/util: add ensure macro
  2025-10-31 15:25 [PATCH 0/4] rust: improvements to errors Paolo Bonzini
@ 2025-10-31 15:25 ` Paolo Bonzini
  2025-11-04 15:17   ` Zhao Liu
  2025-10-31 15:25 ` [PATCH 2/4] rust/util: use anyhow's native chaining capabilities Paolo Bonzini
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 9+ messages in thread
From: Paolo Bonzini @ 2025-10-31 15:25 UTC (permalink / raw)
  To: qemu-devel; +Cc: qemu-rust

The macro is similar to anyhow::ensure but uses QEMU's variation
on anyhow::Error.  It can be used to easily check a condition
and format an error message.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 rust/hw/timer/hpet/src/device.rs | 21 ++++++----
 rust/hw/timer/hpet/src/fw_cfg.rs |  7 ++--
 rust/util/src/error.rs           | 71 ++++++++++++++++++++++++++++++++
 3 files changed, 86 insertions(+), 13 deletions(-)

diff --git a/rust/hw/timer/hpet/src/device.rs b/rust/hw/timer/hpet/src/device.rs
index 23f2eefd1cd..3564aa79c6e 100644
--- a/rust/hw/timer/hpet/src/device.rs
+++ b/rust/hw/timer/hpet/src/device.rs
@@ -25,7 +25,10 @@
     bindings::{address_space_memory, address_space_stl_le, hwaddr},
     MemoryRegion, MemoryRegionOps, MemoryRegionOpsBuilder, MEMTXATTRS_UNSPECIFIED,
 };
-use util::timer::{Timer, CLOCK_VIRTUAL, NANOSECONDS_PER_SECOND};
+use util::{
+    ensure,
+    timer::{Timer, CLOCK_VIRTUAL, NANOSECONDS_PER_SECOND},
+};
 
 use crate::fw_cfg::HPETFwConfig;
 
@@ -728,14 +731,14 @@ fn post_init(&self) {
     }
 
     fn realize(&self) -> util::Result<()> {
-        if self.num_timers < HPET_MIN_TIMERS || self.num_timers > HPET_MAX_TIMERS {
-            Err(format!(
-                "hpet.num_timers must be between {HPET_MIN_TIMERS} and {HPET_MAX_TIMERS}"
-            ))?;
-        }
-        if self.int_route_cap == 0 {
-            Err("hpet.hpet-intcap property not initialized")?;
-        }
+        ensure!(
+            (HPET_MIN_TIMERS..=HPET_MAX_TIMERS).contains(&self.num_timers),
+            "hpet.num_timers must be between {HPET_MIN_TIMERS} and {HPET_MAX_TIMERS}"
+        );
+        ensure!(
+            self.int_route_cap != 0,
+            "hpet.hpet-intcap property not initialized"
+        );
 
         self.hpet_id.set(HPETFwConfig::assign_hpet_id()?);
 
diff --git a/rust/hw/timer/hpet/src/fw_cfg.rs b/rust/hw/timer/hpet/src/fw_cfg.rs
index bb4ea8909ad..777fc8ef45e 100644
--- a/rust/hw/timer/hpet/src/fw_cfg.rs
+++ b/rust/hw/timer/hpet/src/fw_cfg.rs
@@ -5,6 +5,7 @@
 use std::ptr::addr_of_mut;
 
 use common::Zeroable;
+use util::{self, ensure};
 
 /// Each `HPETState` represents a Event Timer Block. The v1 spec supports
 /// up to 8 blocks. QEMU only uses 1 block (in PC machine).
@@ -36,7 +37,7 @@ unsafe impl Zeroable for HPETFwConfig {}
 };
 
 impl HPETFwConfig {
-    pub(crate) fn assign_hpet_id() -> Result<usize, &'static str> {
+    pub(crate) fn assign_hpet_id() -> util::Result<usize> {
         assert!(bql::is_locked());
         // SAFETY: all accesses go through these methods, which guarantee
         // that the accesses are protected by the BQL.
@@ -47,9 +48,7 @@ pub(crate) fn assign_hpet_id() -> Result<usize, &'static str> {
             fw_cfg.count = 0;
         }
 
-        if fw_cfg.count == 8 {
-            Err("Only 8 instances of HPET are allowed")?;
-        }
+        ensure!(fw_cfg.count != 8, "Only 8 instances of HPET are allowed");
 
         let id: usize = fw_cfg.count.into();
         fw_cfg.count += 1;
diff --git a/rust/util/src/error.rs b/rust/util/src/error.rs
index bfa5a8685bc..2a57c7fd5fd 100644
--- a/rust/util/src/error.rs
+++ b/rust/util/src/error.rs
@@ -86,6 +86,19 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
     }
 }
 
+impl From<Cow<'static, str>> for Error {
+    #[track_caller]
+    fn from(msg: Cow<'static, str>) -> Self {
+        let location = panic::Location::caller();
+        Error {
+            msg: Some(msg),
+            cause: None,
+            file: location.file(),
+            line: location.line(),
+        }
+    }
+}
+
 impl From<String> for Error {
     #[track_caller]
     fn from(msg: String) -> Self {
@@ -126,6 +139,17 @@ fn from(error: anyhow::Error) -> Self {
 }
 
 impl Error {
+    #[track_caller]
+    #[doc(hidden)]
+    pub fn format(args: fmt::Arguments) -> Self {
+        if let Some(msg) = args.as_str() {
+            Self::from(msg)
+        } else {
+            let msg = fmt::format(args);
+            Self::from(msg)
+        }
+    }
+
     /// Create a new error, prepending `msg` to the
     /// description of `cause`
     #[track_caller]
@@ -311,6 +335,53 @@ unsafe fn cloned_from_foreign(c_error: *const bindings::Error) -> Self {
     }
 }
 
+/// Ensure that a condition is true, returning an error if it is false.
+///
+/// This macro is similar to [`anyhow::ensure`] but returns a QEMU [`Result`].
+/// If the condition evaluates to `false`, the macro returns early with an error
+/// constructed from the provided message.
+///
+/// # Examples
+///
+/// ```
+/// # use util::{ensure, Result};
+/// # fn check_positive(x: i32) -> Result<()> {
+/// ensure!(x > 0, "value must be positive");
+/// #   Ok(())
+/// # }
+/// ```
+///
+/// ```
+/// # use util::{ensure, Result};
+/// # const MIN: i32 = 123;
+/// # const MAX: i32 = 456;
+/// # fn check_range(x: i32) -> Result<()> {
+/// ensure!(
+///     x >= MIN && x <= MAX,
+///     "{} not between {} and {}",
+///     x,
+///     MIN,
+///     MAX
+/// );
+/// #   Ok(())
+/// # }
+/// ```
+#[macro_export]
+macro_rules! ensure {
+    ($cond:expr, $fmt:literal, $($arg:tt)*) => {
+        if !$cond {
+            let e = $crate::Error::format(format_args!($fmt, $($arg)*));
+            return $crate::Result::Err(e);
+        }
+    };
+    ($cond:expr, $err:expr $(,)?) => {
+        if !$cond {
+            let s = ::std::borrow::Cow::<'static, str>::from($err);
+            return $crate::Result::Err(s.into());
+        }
+    };
+}
+
 #[cfg(test)]
 mod tests {
     use std::ffi::CStr;
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 2/4] rust/util: use anyhow's native chaining capabilities
  2025-10-31 15:25 [PATCH 0/4] rust: improvements to errors Paolo Bonzini
  2025-10-31 15:25 ` [PATCH 1/4] rust/util: add ensure macro Paolo Bonzini
@ 2025-10-31 15:25 ` Paolo Bonzini
  2025-11-04 15:31   ` Zhao Liu
  2025-10-31 15:25 ` [PATCH 3/4] rust/util: replace Error::err_or_unit/err_or_else with Error::with_errp Paolo Bonzini
  2025-10-31 15:25 ` [PATCH 4/4] rust: pull error_fatal out of SysbusDeviceMethods::sysbus_realize Paolo Bonzini
  3 siblings, 1 reply; 9+ messages in thread
From: Paolo Bonzini @ 2025-10-31 15:25 UTC (permalink / raw)
  To: qemu-devel; +Cc: qemu-rust

This simplifies conversions, making it possible to convert any error
into a QEMU util::Error with ".into()" (and therefore with "?").

The cost is having a separate constructor for when the error is a simple
string, but that is made easier by the ensure! macro.  If necessary,
another macro similar to "anyhow!" can be returned, but for now there
is no need for that.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 rust/util/src/error.rs | 160 +++++++++++++++--------------------------
 1 file changed, 59 insertions(+), 101 deletions(-)

diff --git a/rust/util/src/error.rs b/rust/util/src/error.rs
index 2a57c7fd5fd..11b574ca593 100644
--- a/rust/util/src/error.rs
+++ b/rust/util/src/error.rs
@@ -38,6 +38,7 @@
     borrow::Cow,
     ffi::{c_char, c_int, c_void, CStr},
     fmt::{self, Display},
+    ops::Deref,
     panic, ptr,
 };
 
@@ -49,118 +50,85 @@
 
 #[derive(Debug)]
 pub struct Error {
-    msg: Option<Cow<'static, str>>,
-    /// Appends the print string of the error to the msg if not None
-    cause: Option<anyhow::Error>,
+    cause: anyhow::Error,
     file: &'static str,
     line: u32,
 }
 
-impl std::error::Error for Error {
-    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
-        self.cause.as_ref().map(AsRef::as_ref)
-    }
+impl Deref for Error {
+    type Target = anyhow::Error;
 
-    #[allow(deprecated)]
-    fn description(&self) -> &str {
-        self.msg
-            .as_deref()
-            .or_else(|| self.cause.as_deref().map(std::error::Error::description))
-            .expect("no message nor cause?")
+    fn deref(&self) -> &Self::Target {
+        &self.cause
     }
 }
 
 impl Display for Error {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let mut prefix = "";
-        if let Some(ref msg) = self.msg {
-            write!(f, "{msg}")?;
-            prefix = ": ";
-        }
-        if let Some(ref cause) = self.cause {
-            write!(f, "{prefix}{cause}")?;
-        } else if prefix.is_empty() {
-            panic!("no message nor cause?");
-        }
-        Ok(())
+        Display::fmt(&format_args!("{:#}", self.cause), f)
     }
 }
 
-impl From<Cow<'static, str>> for Error {
+impl<E> From<E> for Error
+where
+    anyhow::Error: From<E>,
+{
     #[track_caller]
-    fn from(msg: Cow<'static, str>) -> Self {
-        let location = panic::Location::caller();
-        Error {
-            msg: Some(msg),
-            cause: None,
-            file: location.file(),
-            line: location.line(),
-        }
-    }
-}
-
-impl From<String> for Error {
-    #[track_caller]
-    fn from(msg: String) -> Self {
-        let location = panic::Location::caller();
-        Error {
-            msg: Some(Cow::Owned(msg)),
-            cause: None,
-            file: location.file(),
-            line: location.line(),
-        }
-    }
-}
-
-impl From<&'static str> for Error {
-    #[track_caller]
-    fn from(msg: &'static str) -> Self {
-        let location = panic::Location::caller();
-        Error {
-            msg: Some(Cow::Borrowed(msg)),
-            cause: None,
-            file: location.file(),
-            line: location.line(),
-        }
-    }
-}
-
-impl From<anyhow::Error> for Error {
-    #[track_caller]
-    fn from(error: anyhow::Error) -> Self {
-        let location = panic::Location::caller();
-        Error {
-            msg: None,
-            cause: Some(error),
-            file: location.file(),
-            line: location.line(),
-        }
+    fn from(src: E) -> Self {
+        Self::new(anyhow::Error::from(src))
     }
 }
 
 impl Error {
+    /// Create a new error from an [`anyhow::Error`].
+    ///
+    /// This wraps the error with QEMU's location tracking information.
+    /// Most code should use the `?` operator instead of calling this directly.
+    #[track_caller]
+    pub fn new(cause: anyhow::Error) -> Self {
+        let location = panic::Location::caller();
+        Self {
+            cause,
+            file: location.file(),
+            line: location.line(),
+        }
+    }
+
+    /// Create a new error from a string message.
+    ///
+    /// This is a convenience wrapper around [`Error::new`] for simple string
+    /// errors. Most code should use the [`ensure!`](crate::ensure) macro
+    /// instead of calling this directly.
+    #[track_caller]
+    pub fn msg(src: impl Into<Cow<'static, str>>) -> Self {
+        Self::new(anyhow::Error::msg(src.into()))
+    }
+
     #[track_caller]
     #[doc(hidden)]
+    #[inline(always)]
     pub fn format(args: fmt::Arguments) -> Self {
-        if let Some(msg) = args.as_str() {
-            Self::from(msg)
-        } else {
-            let msg = fmt::format(args);
-            Self::from(msg)
-        }
+        // anyhow::Error::msg will allocate anyway, might as well let fmt::format doit.
+        let msg = fmt::format(args);
+        Self::new(anyhow::Error::msg(msg))
     }
 
     /// Create a new error, prepending `msg` to the
     /// description of `cause`
     #[track_caller]
     pub fn with_error(msg: impl Into<Cow<'static, str>>, cause: impl Into<anyhow::Error>) -> Self {
-        let location = panic::Location::caller();
-        Error {
-            msg: Some(msg.into()),
-            cause: Some(cause.into()),
-            file: location.file(),
-            line: location.line(),
+        fn do_with_error(
+            msg: Cow<'static, str>,
+            cause: anyhow::Error,
+            location: &'static panic::Location<'static>,
+        ) -> Error {
+            Error {
+                cause: cause.context(msg),
+                file: location.file(),
+                line: location.line(),
+            }
         }
+        do_with_error(msg.into(), cause.into(), panic::Location::caller())
     }
 
     /// Consume a result, returning `false` if it is an error and
@@ -326,8 +294,7 @@ unsafe fn cloned_from_foreign(c_error: *const bindings::Error) -> Self {
             };
 
             Error {
-                msg: FromForeign::cloned_from_foreign(error.msg),
-                cause: None,
+                cause: anyhow::Error::msg(String::cloned_from_foreign(error.msg)),
                 file: file.unwrap(),
                 line: error.line as u32,
             }
@@ -376,8 +343,8 @@ macro_rules! ensure {
     };
     ($cond:expr, $err:expr $(,)?) => {
         if !$cond {
-            let s = ::std::borrow::Cow::<'static, str>::from($err);
-            return $crate::Result::Err(s.into());
+            let e = $crate::Error::msg($err);
+            return $crate::Result::Err(e);
         }
     };
 }
@@ -416,19 +383,10 @@ unsafe fn error_get_pretty<'a>(local_err: *mut bindings::Error) -> &'a CStr {
         unsafe { CStr::from_ptr(bindings::error_get_pretty(local_err)) }
     }
 
-    #[test]
-    #[allow(deprecated)]
-    fn test_description() {
-        use std::error::Error;
-
-        assert_eq!(super::Error::from("msg").description(), "msg");
-        assert_eq!(super::Error::from("msg".to_owned()).description(), "msg");
-    }
-
     #[test]
     fn test_display() {
-        assert_eq!(&*format!("{}", Error::from("msg")), "msg");
-        assert_eq!(&*format!("{}", Error::from("msg".to_owned())), "msg");
+        assert_eq!(&*format!("{}", Error::msg("msg")), "msg");
+        assert_eq!(&*format!("{}", Error::msg("msg".to_owned())), "msg");
         assert_eq!(&*format!("{}", Error::from(anyhow!("msg"))), "msg");
 
         assert_eq!(
@@ -445,7 +403,7 @@ fn test_bool_or_propagate() {
             assert!(Error::bool_or_propagate(Ok(()), &mut local_err));
             assert_eq!(local_err, ptr::null_mut());
 
-            let my_err = Error::from("msg");
+            let my_err = Error::msg("msg");
             assert!(!Error::bool_or_propagate(Err(my_err), &mut local_err));
             assert_ne!(local_err, ptr::null_mut());
             assert_eq!(error_get_pretty(local_err), c"msg");
@@ -462,7 +420,7 @@ fn test_ptr_or_propagate() {
             assert_eq!(String::from_foreign(ret), "abc");
             assert_eq!(local_err, ptr::null_mut());
 
-            let my_err = Error::from("msg");
+            let my_err = Error::msg("msg");
             assert_eq!(
                 Error::ptr_or_propagate(Err::<String, _>(my_err), &mut local_err),
                 ptr::null_mut()
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 3/4] rust/util: replace Error::err_or_unit/err_or_else with Error::with_errp
  2025-10-31 15:25 [PATCH 0/4] rust: improvements to errors Paolo Bonzini
  2025-10-31 15:25 ` [PATCH 1/4] rust/util: add ensure macro Paolo Bonzini
  2025-10-31 15:25 ` [PATCH 2/4] rust/util: use anyhow's native chaining capabilities Paolo Bonzini
@ 2025-10-31 15:25 ` Paolo Bonzini
  2025-11-04 15:44   ` Zhao Liu
  2025-10-31 15:25 ` [PATCH 4/4] rust: pull error_fatal out of SysbusDeviceMethods::sysbus_realize Paolo Bonzini
  3 siblings, 1 reply; 9+ messages in thread
From: Paolo Bonzini @ 2025-10-31 15:25 UTC (permalink / raw)
  To: qemu-devel; +Cc: qemu-rust

Introduce a simpler function that hides the creation of the Error**.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 rust/util/src/error.rs | 52 ++++++++++++++++--------------------------
 1 file changed, 20 insertions(+), 32 deletions(-)

diff --git a/rust/util/src/error.rs b/rust/util/src/error.rs
index 11b574ca593..346577e2e53 100644
--- a/rust/util/src/error.rs
+++ b/rust/util/src/error.rs
@@ -14,8 +14,7 @@
 //!   [`ptr_or_propagate`](crate::Error::ptr_or_propagate) can be used to build
 //!   a C return value while also propagating an error condition
 //!
-//! * [`err_or_else`](crate::Error::err_or_else) and
-//!   [`err_or_unit`](crate::Error::err_or_unit) can be used to build a `Result`
+//! * [`with_errp`](crate::Error::with_errp) can be used to build a `Result`
 //!
 //! This module is most commonly used at the boundary between C and Rust code;
 //! other code will usually access it through the
@@ -213,35 +212,21 @@ pub unsafe fn propagate(self, errp: *mut *mut bindings::Error) {
         }
     }
 
-    /// Convert a C `Error*` into a Rust `Result`, using
-    /// `Ok(())` if `c_error` is NULL.  Free the `Error*`.
+    /// Pass a C `Error*` to the closure, and convert the result
+    /// (either the return value of the closure, or the error)
+    /// into a Rust `Result`.
     ///
     /// # Safety
     ///
-    /// `c_error` must be `NULL` or valid; typically it was initialized
-    /// with `ptr::null_mut()` and passed by reference to a C function.
-    pub unsafe fn err_or_unit(c_error: *mut bindings::Error) -> Result<()> {
-        // SAFETY: caller guarantees c_error is valid
-        unsafe { Self::err_or_else(c_error, || ()) }
-    }
+    /// One exit from `f`, `c_error` must be unchanged or point to a
+    /// valid C [`struct Error`](bindings::Error).
+    pub unsafe fn with_errp<T, F: FnOnce(&mut *mut bindings::Error) -> T>(f: F) -> Result<T> {
+        let mut c_error: *mut bindings::Error = ptr::null_mut();
 
-    /// Convert a C `Error*` into a Rust `Result`, calling `f()` to
-    /// obtain an `Ok` value if `c_error` is NULL.  Free the `Error*`.
-    ///
-    /// # Safety
-    ///
-    /// `c_error` must be `NULL` or point to a valid C [`struct
-    /// Error`](bindings::Error); typically it was initialized with
-    /// `ptr::null_mut()` and passed by reference to a C function.
-    pub unsafe fn err_or_else<T, F: FnOnce() -> T>(
-        c_error: *mut bindings::Error,
-        f: F,
-    ) -> Result<T> {
-        // SAFETY: caller guarantees c_error is valid
-        let err = unsafe { Option::<Self>::from_foreign(c_error) };
-        match err {
-            None => Ok(f()),
-            Some(err) => Err(err),
+        // SAFETY: guaranteed by the postcondition of `f`
+        match (f(&mut c_error), unsafe { c_error.into_native() }) {
+            (result, None) => Ok(result),
+            (_, Some(err)) => Err(err),
         }
     }
 }
@@ -432,13 +417,16 @@ fn test_ptr_or_propagate() {
     }
 
     #[test]
-    fn test_err_or_unit() {
+    fn test_with_errp() {
         unsafe {
-            let result = Error::err_or_unit(ptr::null_mut());
-            assert_match!(result, Ok(()));
+            let result = Error::with_errp(|_errp| true);
+            assert_match!(result, Ok(true));
 
-            let err = error_for_test(c"msg");
-            let err = Error::err_or_unit(err.into_inner()).unwrap_err();
+            let err = Error::with_errp(|errp| {
+                *errp = error_for_test(c"msg").into_inner();
+                false
+            })
+            .unwrap_err();
             assert_eq!(&*format!("{err}"), "msg");
         }
     }
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 4/4] rust: pull error_fatal out of SysbusDeviceMethods::sysbus_realize
  2025-10-31 15:25 [PATCH 0/4] rust: improvements to errors Paolo Bonzini
                   ` (2 preceding siblings ...)
  2025-10-31 15:25 ` [PATCH 3/4] rust/util: replace Error::err_or_unit/err_or_else with Error::with_errp Paolo Bonzini
@ 2025-10-31 15:25 ` Paolo Bonzini
  2025-11-04 15:47   ` Zhao Liu
  3 siblings, 1 reply; 9+ messages in thread
From: Paolo Bonzini @ 2025-10-31 15:25 UTC (permalink / raw)
  To: qemu-devel; +Cc: qemu-rust

Return a Result<()> from the method, and "unwrap" it into error_fatal
in the caller.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 rust/hw/char/pl011/src/device.rs |  4 ++--
 rust/hw/core/src/sysbus.rs       | 13 ++++++-------
 rust/util/src/error.rs           | 31 ++++++++++++++++++++++++++++++-
 rust/util/src/lib.rs             |  2 +-
 4 files changed, 39 insertions(+), 11 deletions(-)

diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs
index 5e9b13fdf92..26be6ef57f2 100644
--- a/rust/hw/char/pl011/src/device.rs
+++ b/rust/hw/char/pl011/src/device.rs
@@ -17,7 +17,7 @@
 };
 use qom::{prelude::*, ObjectImpl, Owned, ParentField, ParentInit};
 use system::{hwaddr, MemoryRegion, MemoryRegionOps, MemoryRegionOpsBuilder};
-use util::{log::Log, log_mask_ln};
+use util::{ResultExt, log::Log, log_mask_ln};
 
 use crate::registers::{self, Interrupt, RegisterOffset};
 
@@ -697,7 +697,7 @@ pub fn post_load(&self, _version_id: u8) -> Result<(), migration::InvalidError>
         let chr = unsafe { Owned::<Chardev>::from(&*chr) };
         dev.prop_set_chr("chardev", &chr);
     }
-    dev.sysbus_realize();
+    dev.sysbus_realize().unwrap_fatal();
     dev.mmio_map(0, addr);
     dev.connect_irq(0, &irq);
 
diff --git a/rust/hw/core/src/sysbus.rs b/rust/hw/core/src/sysbus.rs
index 282315fce99..68165e89295 100644
--- a/rust/hw/core/src/sysbus.rs
+++ b/rust/hw/core/src/sysbus.rs
@@ -4,12 +4,13 @@
 
 //! Bindings to access `sysbus` functionality from Rust.
 
-use std::{ffi::CStr, ptr::addr_of_mut};
+use std::ffi::CStr;
 
 pub use bindings::SysBusDeviceClass;
 use common::Opaque;
 use qom::{prelude::*, Owned};
 use system::MemoryRegion;
+use util::{Error, Result};
 
 use crate::{
     bindings,
@@ -107,14 +108,12 @@ fn connect_irq(&self, id: u32, irq: &Owned<IRQState>) {
         }
     }
 
-    fn sysbus_realize(&self) {
-        // TODO: return an Error
+    fn sysbus_realize(&self) -> Result<()> {
         assert!(bql::is_locked());
         unsafe {
-            bindings::sysbus_realize(
-                self.upcast().as_mut_ptr(),
-                addr_of_mut!(util::bindings::error_fatal),
-            );
+            Error::with_errp(|errp| {
+                bindings::sysbus_realize(self.upcast().as_mut_ptr(), errp);
+            })
         }
     }
 }
diff --git a/rust/util/src/error.rs b/rust/util/src/error.rs
index 346577e2e53..4edceff42f3 100644
--- a/rust/util/src/error.rs
+++ b/rust/util/src/error.rs
@@ -38,7 +38,8 @@
     ffi::{c_char, c_int, c_void, CStr},
     fmt::{self, Display},
     ops::Deref,
-    panic, ptr,
+    panic,
+    ptr::{self, addr_of_mut},
 };
 
 use foreign::{prelude::*, OwnedPointer};
@@ -231,6 +232,34 @@ pub unsafe fn with_errp<T, F: FnOnce(&mut *mut bindings::Error) -> T>(f: F) -> R
     }
 }
 
+/// Extension trait for `std::result::Result`, providing extra
+/// methods when the error type can be converted into a QEMU
+/// Error.
+pub trait ResultExt {
+    /// The success type `T` in `Result<T, E>`.
+    type OkType;
+
+    /// Report a fatal error and exit QEMU, or return the success value.
+    /// Note that, unlike [`unwrap()`](std::result::Result::unwrap), this
+    /// is not an abort and can be used for user errors.
+    fn unwrap_fatal(self) -> Self::OkType;
+}
+
+impl<T, E> ResultExt for std::result::Result<T, E>
+where
+    Error: From<E>,
+{
+    type OkType = T;
+
+    fn unwrap_fatal(self) -> T {
+        // SAFETY: errp is valid
+        self.map_err(|err| unsafe {
+            Error::from(err).propagate(addr_of_mut!(bindings::error_fatal))
+        })
+        .unwrap()
+    }
+}
+
 impl FreeForeign for Error {
     type Foreign = bindings::Error;
 
diff --git a/rust/util/src/lib.rs b/rust/util/src/lib.rs
index 16c89b95174..d14aa14ca77 100644
--- a/rust/util/src/lib.rs
+++ b/rust/util/src/lib.rs
@@ -6,4 +6,4 @@
 pub mod module;
 pub mod timer;
 
-pub use error::{Error, Result};
+pub use error::{Error, Result, ResultExt};
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH 1/4] rust/util: add ensure macro
  2025-10-31 15:25 ` [PATCH 1/4] rust/util: add ensure macro Paolo Bonzini
@ 2025-11-04 15:17   ` Zhao Liu
  0 siblings, 0 replies; 9+ messages in thread
From: Zhao Liu @ 2025-11-04 15:17 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: qemu-devel, qemu-rust

On Fri, Oct 31, 2025 at 04:25:36PM +0100, Paolo Bonzini wrote:
> Date: Fri, 31 Oct 2025 16:25:36 +0100
> From: Paolo Bonzini <pbonzini@redhat.com>
> Subject: [PATCH 1/4] rust/util: add ensure macro
> X-Mailer: git-send-email 2.51.1
> 
> The macro is similar to anyhow::ensure but uses QEMU's variation
> on anyhow::Error.  It can be used to easily check a condition
> and format an error message.
> 
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
>  rust/hw/timer/hpet/src/device.rs | 21 ++++++----
>  rust/hw/timer/hpet/src/fw_cfg.rs |  7 ++--
>  rust/util/src/error.rs           | 71 ++++++++++++++++++++++++++++++++
>  3 files changed, 86 insertions(+), 13 deletions(-)

Good!

Reviewed-by: Zhao Liu <zhao1.liu@intel.com>



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 2/4] rust/util: use anyhow's native chaining capabilities
  2025-10-31 15:25 ` [PATCH 2/4] rust/util: use anyhow's native chaining capabilities Paolo Bonzini
@ 2025-11-04 15:31   ` Zhao Liu
  0 siblings, 0 replies; 9+ messages in thread
From: Zhao Liu @ 2025-11-04 15:31 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: qemu-devel, qemu-rust

On Fri, Oct 31, 2025 at 04:25:37PM +0100, Paolo Bonzini wrote:
> Date: Fri, 31 Oct 2025 16:25:37 +0100
> From: Paolo Bonzini <pbonzini@redhat.com>
> Subject: [PATCH 2/4] rust/util: use anyhow's native chaining capabilities
> X-Mailer: git-send-email 2.51.1
> 
> This simplifies conversions, making it possible to convert any error
> into a QEMU util::Error with ".into()" (and therefore with "?").
> 
> The cost is having a separate constructor for when the error is a simple
> string, but that is made easier by the ensure! macro.  If necessary,
> another macro similar to "anyhow!" can be returned, but for now there
> is no need for that.
> 
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
>  rust/util/src/error.rs | 160 +++++++++++++++--------------------------
>  1 file changed, 59 insertions(+), 101 deletions(-)

LGTM,

Reviewed-by: Zhao Liu <zhao1.liu@intel.com>



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 3/4] rust/util: replace Error::err_or_unit/err_or_else with Error::with_errp
  2025-10-31 15:25 ` [PATCH 3/4] rust/util: replace Error::err_or_unit/err_or_else with Error::with_errp Paolo Bonzini
@ 2025-11-04 15:44   ` Zhao Liu
  0 siblings, 0 replies; 9+ messages in thread
From: Zhao Liu @ 2025-11-04 15:44 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: qemu-devel, qemu-rust

On Fri, Oct 31, 2025 at 04:25:38PM +0100, Paolo Bonzini wrote:
> Date: Fri, 31 Oct 2025 16:25:38 +0100
> From: Paolo Bonzini <pbonzini@redhat.com>
> Subject: [PATCH 3/4] rust/util: replace Error::err_or_unit/err_or_else with
>  Error::with_errp
> X-Mailer: git-send-email 2.51.1
> 
> Introduce a simpler function that hides the creation of the Error**.
> 
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
>  rust/util/src/error.rs | 52 ++++++++++++++++--------------------------
>  1 file changed, 20 insertions(+), 32 deletions(-)

Reviewed-by: Zhao Liu <zhao1.liu@intel.com>



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 4/4] rust: pull error_fatal out of SysbusDeviceMethods::sysbus_realize
  2025-10-31 15:25 ` [PATCH 4/4] rust: pull error_fatal out of SysbusDeviceMethods::sysbus_realize Paolo Bonzini
@ 2025-11-04 15:47   ` Zhao Liu
  0 siblings, 0 replies; 9+ messages in thread
From: Zhao Liu @ 2025-11-04 15:47 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: qemu-devel, qemu-rust

On Fri, Oct 31, 2025 at 04:25:39PM +0100, Paolo Bonzini wrote:
> Date: Fri, 31 Oct 2025 16:25:39 +0100
> From: Paolo Bonzini <pbonzini@redhat.com>
> Subject: [PATCH 4/4] rust: pull error_fatal out of
>  SysbusDeviceMethods::sysbus_realize
> X-Mailer: git-send-email 2.51.1
> 
> Return a Result<()> from the method, and "unwrap" it into error_fatal
> in the caller.
> 
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
>  rust/hw/char/pl011/src/device.rs |  4 ++--
>  rust/hw/core/src/sysbus.rs       | 13 ++++++-------
>  rust/util/src/error.rs           | 31 ++++++++++++++++++++++++++++++-
>  rust/util/src/lib.rs             |  2 +-
>  4 files changed, 39 insertions(+), 11 deletions(-)

Reviewed-by: Zhao Liu <zhao1.liu@intel.com>



^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2025-11-04 15:25 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-10-31 15:25 [PATCH 0/4] rust: improvements to errors Paolo Bonzini
2025-10-31 15:25 ` [PATCH 1/4] rust/util: add ensure macro Paolo Bonzini
2025-11-04 15:17   ` Zhao Liu
2025-10-31 15:25 ` [PATCH 2/4] rust/util: use anyhow's native chaining capabilities Paolo Bonzini
2025-11-04 15:31   ` Zhao Liu
2025-10-31 15:25 ` [PATCH 3/4] rust/util: replace Error::err_or_unit/err_or_else with Error::with_errp Paolo Bonzini
2025-11-04 15:44   ` Zhao Liu
2025-10-31 15:25 ` [PATCH 4/4] rust: pull error_fatal out of SysbusDeviceMethods::sysbus_realize Paolo Bonzini
2025-11-04 15:47   ` Zhao Liu

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).