Hi (adding Sebastian, one of the glib-rs developers in CC)
On Mon, Jul 1, 2024 at 7:02 PM Paolo Bonzini <pbonz...@redhat.com> wrote: > The qemu::util::foreign module provides: > > - A trait for structs that can be converted to a C ("foreign") > representation > (CloneToForeign) > > - A trait for structs that can be built from a C ("foreign") representation > (FromForeign), and the utility IntoNative that can be used with less > typing > (similar to the standard library's From and Into pair) > > - Automatic implementations of the above traits for Option<>, supporting > NULL > pointers > > - A wrapper for a pointer that automatically frees the contained data. If > a struct XYZ implements CloneToForeign, you can build an > OwnedPointer<XYZ> > and it will free the contents automatically unless you retrieve it with > owned_ptr.into_inner() > You worry about technical debt, and I do too. Here you introduce quite different traits than what glib-rs offers. We already touched this subject 2y ago, my opinion didn't change much ( https://patchew.org/QEMU/20210907121943.3498701-1-marcandre.lur...@redhat.com/20210907121943.3498701-13-marcandre.lur...@redhat.com/). Also, you don't offer the equivalent of "to_glib_none" which uses a temporary stash and is quite useful, as a majority of functions don't take ownership. Because much of our code is using GLib types and API style, I think we should strive for something that is close (if not just the same) to what glib-rs offers. It's already hard enough to handle one binding concept, having 2 will only make the matter worse. Consider a type like GHashTable<GUuid, QOM>, it will be very annoying to deal with if we have different bindings traits and implementations and we will likely end up duplicating glib-rs effort. As for naming & consistency, glib-rs settled on something clearer imho: from_glib_full from_glib_none to_glib_full to_glib_none vs from_foreign cloned_from_foreign clone_to_foreign /nothing/ but overall, this is still close enough that we shouldn't reinvent it. It may be worth studying what the kernel offers, I haven't checked yet. > > Signed-off-by: Paolo Bonzini <pbonz...@redhat.com> > --- > qemu/src/lib.rs | 6 + > qemu/src/util/foreign.rs | 247 +++++++++++++++++++++++++++++++++++++++ > qemu/src/util/mod.rs | 1 + > 3 files changed, 254 insertions(+) > create mode 100644 qemu/src/util/foreign.rs > create mode 100644 qemu/src/util/mod.rs > > diff --git a/qemu/src/lib.rs b/qemu/src/lib.rs > index fce21d7..c48edcf 100644 > --- a/qemu/src/lib.rs > +++ b/qemu/src/lib.rs > @@ -2,3 +2,9 @@ > #![allow(dead_code)] > > pub mod bindings; > + > +pub mod util; > +pub use util::foreign::CloneToForeign; > +pub use util::foreign::FromForeign; > +pub use util::foreign::IntoNative; > +pub use util::foreign::OwnedPointer; > diff --git a/qemu/src/util/foreign.rs b/qemu/src/util/foreign.rs > new file mode 100644 > index 0000000..a591925 > --- /dev/null > +++ b/qemu/src/util/foreign.rs > @@ -0,0 +1,247 @@ > +// TODO: change to use .cast() etc. > +#![allow(clippy::ptr_as_ptr)] > + > +/// Traits to map between C structs and native Rust types. > +/// Similar to glib-rs but a bit simpler and possibly more > +/// idiomatic. > +use std::borrow::Cow; > +use std::fmt; > +use std::fmt::Debug; > +use std::mem; > +use std::ptr; > + > +/// A type for which there is a canonical representation as a C datum. > +pub trait CloneToForeign { > + /// The representation of `Self` as a C datum. Typically a > + /// `struct`, though there are exceptions for example `c_char` > + /// for strings, since C strings are of `char *` type). > + type Foreign; > + > + /// Free the C datum pointed to by `p`. > + /// > + /// # Safety > + /// > + /// `p` must be `NULL` or point to valid data. > + unsafe fn free_foreign(p: *mut Self::Foreign); > + > + /// Convert a native Rust object to a foreign C struct, copying > + /// everything pointed to by `self` (same as `to_glib_full` in > `glib-rs`) > + fn clone_to_foreign(&self) -> OwnedPointer<Self>; > + > + /// Convert a native Rust object to a foreign C pointer, copying > + /// everything pointed to by `self`. The returned pointer must > + /// be freed with the `free_foreign` associated function. > + fn clone_to_foreign_ptr(&self) -> *mut Self::Foreign { > + self.clone_to_foreign().into_inner() > + } > +} > + > +impl<T> CloneToForeign for Option<T> > +where > + T: CloneToForeign, > +{ > + type Foreign = <T as CloneToForeign>::Foreign; > + > + unsafe fn free_foreign(x: *mut Self::Foreign) { > + T::free_foreign(x) > + } > + > + fn clone_to_foreign(&self) -> OwnedPointer<Self> { > + // Same as the underlying implementation, but also convert `None` > + // to a `NULL` pointer. > + self.as_ref() > + .map(CloneToForeign::clone_to_foreign) > + .map(OwnedPointer::into) > + .unwrap_or_default() > + } > +} > + > +impl<T> FromForeign for Option<T> > +where > + T: FromForeign, > +{ > + unsafe fn cloned_from_foreign(p: *const Self::Foreign) -> Self { > + // Same as the underlying implementation, but also accept a > `NULL` pointer. > + if p.is_null() { > + None > + } else { > + Some(T::cloned_from_foreign(p)) > + } > + } > +} > + > +impl<T> CloneToForeign for Box<T> > +where > + T: CloneToForeign, > +{ > + type Foreign = <T as CloneToForeign>::Foreign; > + > + unsafe fn free_foreign(x: *mut Self::Foreign) { > + T::free_foreign(x) > + } > + > + fn clone_to_foreign(&self) -> OwnedPointer<Self> { > + self.as_ref().clone_to_foreign().into() > + } > +} > + > +impl<T> FromForeign for Box<T> > +where > + T: FromForeign, > +{ > + unsafe fn cloned_from_foreign(p: *const Self::Foreign) -> Self { > + Box::new(T::cloned_from_foreign(p)) > + } > +} > + > +impl<'a, B> CloneToForeign for Cow<'a, B> > + where B: 'a + ToOwned + ?Sized + CloneToForeign, > +{ > + type Foreign = B::Foreign; > + > + unsafe fn free_foreign(ptr: *mut B::Foreign) { > + B::free_foreign(ptr); > + } > + > + fn clone_to_foreign(&self) -> OwnedPointer<Self> { > + self.as_ref().clone_to_foreign().into() > + } > +} > + > +/// Convert a C datum into a native Rust object, taking ownership of > +/// the C datum. You should not need to implement this trait > +/// as long as Rust types implement `FromForeign`. > +pub trait IntoNative<T> { > + /// Convert a C datum to a native Rust object, taking ownership of > + /// the pointer or Rust object (same as `from_glib_full` in `glib-rs`) > + /// > + /// # Safety > + /// > + /// `p` must point to valid data, or can be `NULL` if Self is an > + /// `Option` type. It becomes invalid after the function returns. > + unsafe fn into_native(self) -> T; > +} > + > +impl<T, U> IntoNative<U> for *mut T > +where > + U: FromForeign<Foreign = T>, > +{ > + unsafe fn into_native(self) -> U { > + U::from_foreign(self) > + } > +} > + > +/// A type which can be constructed from a canonical representation as a > +/// C datum. > +pub trait FromForeign: CloneToForeign + Sized { > + /// Convert a C datum to a native Rust object, copying everything > + /// pointed to by `p` (same as `from_glib_none` in `glib-rs`) > + /// > + /// # Safety > + /// > + /// `p` must point to valid data, or can be `NULL` is `Self` is an > + /// `Option` type. > + unsafe fn cloned_from_foreign(p: *const Self::Foreign) -> Self; > + > + /// Convert a C datum to a native Rust object, taking ownership of > + /// the pointer or Rust object (same as `from_glib_full` in `glib-rs`) > + /// > + /// The default implementation calls `cloned_from_foreign` and frees > `p`. > + /// > + /// # Safety > + /// > + /// `p` must point to valid data, or can be `NULL` is `Self` is an > + /// `Option` type. `p` becomes invalid after the function returns. > + unsafe fn from_foreign(p: *mut Self::Foreign) -> Self { > + let result = Self::cloned_from_foreign(p); > + Self::free_foreign(p); > + result > + } > +} > + > +pub struct OwnedPointer<T: CloneToForeign + ?Sized> { > + ptr: *mut <T as CloneToForeign>::Foreign, > +} > + > +impl<T: CloneToForeign + ?Sized> OwnedPointer<T> { > + /// Return a new `OwnedPointer` that wraps the pointer `ptr`. > + /// > + /// # Safety > + /// > + /// The pointer must be valid and live until the returned > `OwnedPointer` > + /// is dropped. > + pub unsafe fn new(ptr: *mut <T as CloneToForeign>::Foreign) -> Self { > + OwnedPointer { ptr } > + } > + > + /// Safely create an `OwnedPointer` from one that has the same > + /// freeing function. > + pub fn from<U>(x: OwnedPointer<U>) -> Self > + where > + U: CloneToForeign<Foreign = <T as CloneToForeign>::Foreign> + > ?Sized, > + { > + unsafe { > + // SAFETY: the pointer type and free function are the same, > + // only the type changes > + OwnedPointer::new(x.into_inner()) > + } > + } > + > + /// Safely convert an `OwnedPointer` into one that has the same > + /// freeing function. > + pub fn into<U>(self) -> OwnedPointer<U> > + where > + U: CloneToForeign<Foreign = <T as CloneToForeign>::Foreign>, > + { > + OwnedPointer::from(self) > + } > + > + /// Return the pointer that is stored in the `OwnedPointer`. The > + /// pointer is valid for as long as the `OwnedPointer` itself. > + pub fn as_ptr(&self) -> *const <T as CloneToForeign>::Foreign { > + self.ptr > + } > + > + pub fn as_mut_ptr(&self) -> *mut <T as CloneToForeign>::Foreign { > + self.ptr > + } > + > + /// Return the pointer that is stored in the `OwnedPointer`, > + /// consuming the `OwnedPointer` but not freeing the pointer. > + pub fn into_inner(mut self) -> *mut <T as CloneToForeign>::Foreign { > + let result = mem::replace(&mut self.ptr, ptr::null_mut()); > + mem::forget(self); > + result > + } > +} > + > +impl<T: FromForeign + ?Sized> OwnedPointer<T> { > + /// Convert a C datum to a native Rust object, taking ownership of > + /// the pointer or Rust object (same as `from_glib_full` in `glib-rs`) > + pub fn into_native(self) -> T { > + // SAFETY: the pointer was passed to the unsafe constructor > OwnedPointer::new > + unsafe { T::from_foreign(self.into_inner()) } > + } > +} > + > +impl<T: CloneToForeign + ?Sized> Debug for OwnedPointer<T> { > + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { > + let name = std::any::type_name::<T>(); > + let name = format!("OwnedPointer<{}>", name); > + f.debug_tuple(&name).field(&self.as_ptr()).finish() > + } > +} > + > +impl<T: CloneToForeign + Default + ?Sized> Default for OwnedPointer<T> { > + fn default() -> Self { > + <T as Default>::default().clone_to_foreign() > + } > +} > + > +impl<T: CloneToForeign + ?Sized> Drop for OwnedPointer<T> { > + fn drop(&mut self) { > + let p = mem::replace(&mut self.ptr, ptr::null_mut()); > + // SAFETY: the pointer was passed to the unsafe constructor > OwnedPointer::new > + unsafe { T::free_foreign(p) } > + } > +} > diff --git a/qemu/src/util/mod.rs b/qemu/src/util/mod.rs > new file mode 100644 > index 0000000..be00d0c > --- /dev/null > +++ b/qemu/src/util/mod.rs > @@ -0,0 +1 @@ > +pub mod foreign; > -- > 2.45.2 > > > -- Marc-André Lureau