From: Paolo Bonzini <pbonzini@redhat.com>
To: qemu-devel@nongnu.org
Cc: qemu-rust@nongnu.org, zhao1.liu@intel.com
Subject: [PATCH 03/12] rust: callbacks: allow passing optional callbacks as ()
Date: Fri, 7 Feb 2025 11:16:14 +0100 [thread overview]
Message-ID: <20250207101623.2443552-4-pbonzini@redhat.com> (raw)
In-Reply-To: <20250207101623.2443552-1-pbonzini@redhat.com>
In some cases, callbacks are optional. Using "Some(function)" and "None"
does not work well, because when someone writes "None" the compiler does
not know what to use for "F" in "Option<F>".
Therefore, adopt () to mean a "null" callback. It is possible to enforce
that a callback is valid by adding a "let _: () = F::ASSERT_IS_SOME" before
the invocation of F::call.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
rust/qemu-api/src/callbacks.rs | 97 ++++++++++++++++++++++++++++++++++
1 file changed, 97 insertions(+)
diff --git a/rust/qemu-api/src/callbacks.rs b/rust/qemu-api/src/callbacks.rs
index 314f9dce962..9642a16eb89 100644
--- a/rust/qemu-api/src/callbacks.rs
+++ b/rust/qemu-api/src/callbacks.rs
@@ -79,6 +79,31 @@
/// call_it(&move |_| String::from(x), "hello workd");
/// ```
///
+/// `()` can be used to indicate "no function":
+///
+/// ```
+/// # use qemu_api::callbacks::FnCall;
+/// fn optional<F: for<'a> FnCall<(&'a str,), String>>(_f: &F, s: &str) -> Option<String> {
+/// if F::IS_SOME {
+/// Some(F::call((s,)))
+/// } else {
+/// None
+/// }
+/// }
+///
+/// assert!(optional(&(), "hello world").is_none());
+/// ```
+///
+/// Invoking `F::call` will then be a run-time error.
+///
+/// ```should_panic
+/// # use qemu_api::callbacks::FnCall;
+/// # fn call_it<F: for<'a> FnCall<(&'a str,), String>>(_f: &F, s: &str) -> String {
+/// # F::call((s,))
+/// # }
+/// let s: String = call_it(&(), "hello world"); // panics
+/// ```
+///
/// # Safety
///
/// Because `Self` is a zero-sized type, all instances of the type are
@@ -93,10 +118,70 @@ pub unsafe trait FnCall<Args, R = ()>: 'static + Sync + Sized {
/// Rust 1.79.0+.
const ASSERT_ZERO_SIZED: () = { assert!(mem::size_of::<Self>() == 0) };
+ /// Referring to this constant asserts that the `Self` type is an actual
+ /// function type, which can be used to catch incorrect use of `()`
+ /// at compile time.
+ ///
+ /// # Examples
+ ///
+ /// ```compile_fail
+ /// # use qemu_api::callbacks::FnCall;
+ /// fn call_it<F: for<'a> FnCall<(&'a str,), String>>(_f: &F, s: &str) -> String {
+ /// let _: () = F::ASSERT_IS_SOME;
+ /// F::call((s,))
+ /// }
+ ///
+ /// let s: String = call_it((), "hello world"); // does not compile
+ /// ```
+ ///
+ /// Note that this can be more simply `const { assert!(F::IS_SOME) }` in
+ /// Rust 1.79.0 or newer.
+ const ASSERT_IS_SOME: () = { assert!(Self::IS_SOME) };
+
+ /// `true` if `Self` is an actual function type and not `()`.
+ ///
+ /// # Examples
+ ///
+ /// You can use `IS_SOME` to catch this at compile time:
+ ///
+ /// ```compile_fail
+ /// # use qemu_api::callbacks::FnCall;
+ /// fn call_it<F: for<'a> FnCall<(&'a str,), String>>(_f: &F, s: &str) -> String {
+ /// const { assert!(F::IS_SOME) }
+ /// F::call((s,))
+ /// }
+ ///
+ /// let s: String = call_it((), "hello world"); // does not compile
+ /// ```
+ const IS_SOME: bool;
+
+ /// `false` if `Self` is an actual function type, `true` if it is `()`.
+ fn is_none() -> bool {
+ !Self::IS_SOME
+ }
+
+ /// `true` if `Self` is an actual function type, `false` if it is `()`.
+ fn is_some() -> bool {
+ Self::IS_SOME
+ }
+
/// Call the function with the arguments in args.
fn call(a: Args) -> R;
}
+/// `()` acts as a "null" callback. Using `()` and `function` is nicer
+/// than `None` and `Some(function)`, because the compiler is unable to
+/// infer the type of just `None`. Therefore, the trait itself acts as the
+/// option type, with functions [`FnCall::is_some`] and [`FnCall::is_none`].
+unsafe impl<Args, R> FnCall<Args, R> for () {
+ const IS_SOME: bool = false;
+
+ /// Call the function with the arguments in args.
+ fn call(_a: Args) -> R {
+ panic!("callback not specified")
+ }
+}
+
macro_rules! impl_call {
($($args:ident,)* ) => (
// SAFETY: because each function is treated as a separate type,
@@ -106,6 +191,8 @@ unsafe impl<F, $($args,)* R> FnCall<($($args,)*), R> for F
where
F: 'static + Sync + Sized + Fn($($args, )*) -> R,
{
+ const IS_SOME: bool = true;
+
#[inline(always)]
fn call(a: ($($args,)*)) -> R {
let _: () = Self::ASSERT_ZERO_SIZED;
@@ -141,4 +228,14 @@ fn do_test_call<'a, F: FnCall<(&'a str,), String>>(_f: &F) -> String {
fn test_call() {
assert_eq!(do_test_call(&str::to_owned), "hello world")
}
+
+ // The `_f` parameter is unused but it helps the compiler infer `F`.
+ fn do_test_is_some<'a, F: FnCall<(&'a str,), String>>(_f: &F) {
+ assert!(F::is_some());
+ }
+
+ #[test]
+ fn test_is_some() {
+ do_test_is_some(&str::to_owned);
+ }
}
--
2.48.1
next prev parent reply other threads:[~2025-02-07 10:17 UTC|newest]
Thread overview: 22+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-02-07 10:16 [PATCH 00/12] rust: remaining parts of qdev bindings Paolo Bonzini
2025-02-07 10:16 ` [PATCH 01/12] rust: qom: add reference counting functionality Paolo Bonzini
2025-02-10 14:24 ` Zhao Liu
2025-02-07 10:16 ` [PATCH 02/12] rust: qom: add object creation functionality Paolo Bonzini
2025-02-10 14:30 ` Zhao Liu
2025-02-07 10:16 ` Paolo Bonzini [this message]
2025-02-10 14:31 ` [PATCH 03/12] rust: callbacks: allow passing optional callbacks as () Zhao Liu
2025-02-07 10:16 ` [PATCH 04/12] rust: qdev: add clock creation Paolo Bonzini
2025-02-10 14:40 ` Zhao Liu
2025-02-07 10:16 ` [PATCH 05/12] rust: qom: allow initializing interface vtables Paolo Bonzini
2025-02-07 10:16 ` [PATCH 06/12] rust: qdev: make ObjectImpl a supertrait of DeviceImpl Paolo Bonzini
2025-02-07 10:16 ` [PATCH 07/12] rust: qdev: switch from legacy reset to Resettable Paolo Bonzini
2025-02-11 3:15 ` Zhao Liu
2025-02-07 10:16 ` [PATCH 08/12] rust: bindings: add Send and Sync markers for types that have bindings Paolo Bonzini
2025-02-07 10:16 ` [PATCH 09/12] rust: bindings for MemoryRegionOps Paolo Bonzini
2025-02-11 3:26 ` Zhao Liu
2025-02-07 10:16 ` [PATCH 10/12] rust: irq: define ObjectType for IRQState Paolo Bonzini
2025-02-11 3:31 ` Zhao Liu
2025-02-07 10:16 ` [PATCH 11/12] rust: chardev, qdev: add bindings to qdev_prop_set_chr Paolo Bonzini
2025-02-11 3:34 ` Zhao Liu
2025-02-07 10:16 ` [PATCH 12/12] rust: pl011: convert pl011_create to safe Rust Paolo Bonzini
2025-02-11 3:37 ` Zhao Liu
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250207101623.2443552-4-pbonzini@redhat.com \
--to=pbonzini@redhat.com \
--cc=qemu-devel@nongnu.org \
--cc=qemu-rust@nongnu.org \
--cc=zhao1.liu@intel.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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).