diff --git a/CHANGELOG.md b/CHANGELOG.md index 18bee65..26b2206 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - move to edition 2024, MSRV 1.88 - `CArray`, `CStringArray` and `CRange` have moved to the new `ffi-convert-extra-ctypes` crate. The core `ffi-convert` crate now only contains the conversion traits. +- rewrote most of the documentation ### Fixed - memory leak on array conversion error and performance / correctness improvement on array conversion diff --git a/README.md b/README.md index 01be0ce..3cad725 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,62 @@ # ffi-convert -![ffi-convert](https://github.com/snipsco/snips-utils-rs/workflows/Rust/badge.svg?branch=master&event=push) + +![ffi-convert](https://github.com/sonos/ffi-convert-rs/workflows/Rust/badge.svg?branch=main&event=push) [![ffi-convert on crates.io](https://img.shields.io/crates/v/ffi-convert.svg)](https://crates.io/crates/ffi-convert) [![ffi-convert documentation](https://docs.rs/ffi-convert/badge.svg)](https://docs.rs/ffi-convert/) -**A collection of utilities (functions, traits, data structures, etc ...) to ease conversion between Rust and C-compatible data structures.** -You can learn more about open source projects at Sonos [here](https://tech-blog.sonos.com/posts/three-open-source-sonos-projects-in-rust/) and find the documentation for `ffi-convert` [here](https://docs.rs/ffi-convert). +**Convert between idiomatic Rust values and C-compatible data structures with +a minimum of unsafe ceremony.** + +`ffi-convert` provides two conversion traits — `CReprOf` (Rust → C) and +`AsRust` (C → Rust) — plus `CDrop` and `RawPointerConverter` to handle +ownership of pointer fields, and derive macros that take care of the +boilerplate. + +```rust +use ffi_convert::{AsRust, CDrop, CReprOf}; +use libc::{c_char, c_float}; + +pub struct Pizza { + pub name: String, + pub weight: f32, +} + +#[repr(C)] +#[derive(CReprOf, AsRust, CDrop)] +#[target_type(Pizza)] +pub struct CPizza { + pub name: *const c_char, + pub weight: c_float, +} + +let pizza = Pizza { name: "Margarita".to_owned(), weight: 450.0 }; +let c_pizza = CPizza::c_repr_of(pizza).unwrap(); // Rust -> C +let again: Pizza = c_pizza.as_rust().unwrap(); // C -> Rust +``` + +## Workspace layout + +| Crate | What's in it | +|----------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------| +| [`ffi-convert`](./ffi-convert) | The conversion traits (`CReprOf`, `AsRust`, `CDrop`, `RawPointerConverter`, `RawBorrow`). | +| [`ffi-convert-derive`](./ffi-convert-derive) | `#[derive(...)]` macros for all four conversion traits. Re-exported from `ffi-convert`. | +| [`ffi-convert-extra-ctypes`](./ffi-convert-extra-ctypes) | Optional C-compatible containers: `CArray` (`Vec`), `CStringArray` (`Vec`), `CRange`. | +| [`ffi-convert-tests`](./ffi-convert-tests) | Workspace tests, including C round-trip tests with AddressSanitizer / MemorySanitizer. | + +Full documentation lives on [docs.rs/ffi-convert](https://docs.rs/ffi-convert), +including the type-mapping table, attribute reference, and caveats that apply +to the derives. + +More on open source projects at Sonos +[here](https://tech-blog.sonos.com/posts/three-open-source-sonos-projects-in-rust/). ## License Licensed under either of + * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + at your option. ### Contribution diff --git a/ffi-convert-derive/src/lib.rs b/ffi-convert-derive/src/lib.rs index 1443969..6cbeb32 100644 --- a/ffi-convert-derive/src/lib.rs +++ b/ffi-convert-derive/src/lib.rs @@ -1,4 +1,13 @@ -//! This crate provides ffi_convert derive macros for CReprOf, AsRust and CDrop traits. +//! Derive macros for the conversion traits provided by the +//! [`ffi-convert`](https://docs.rs/ffi-convert) crate. +//! +//! The four derives are also re-exported from `ffi-convert`, so users of +//! `ffi-convert` typically do not need to depend on this crate directly. +//! +//! See the top-level [`ffi-convert`](https://docs.rs/ffi-convert) documentation +//! for the overall design, the type-mapping table, and the caveats that apply +//! to all four derives. The per-macro documentation below lists the supported +//! helper attributes. extern crate proc_macro; @@ -14,6 +23,34 @@ use creprof::impl_creprof_macro; use proc_macro::TokenStream; use rawpointerconverter::impl_rawpointerconverter_macro; +/// Derive [`CReprOf`](../ffi_convert/trait.CReprOf.html) for a struct or unit enum. +/// +/// Generates a consuming conversion from the idiomatic Rust type named in +/// `#[target_type(...)]` to `Self`. C-string fields (pointers to `c_char`) are +/// re-allocated as `CString`s, other pointer fields are boxed via +/// [`RawPointerConverter::into_raw_pointer`](../ffi_convert/trait.RawPointerConverter.html), +/// and remaining fields go through their own `CReprOf` impl. +/// +/// # Struct-level attributes +/// +/// - `#[target_type(Path)]` — **required**. The idiomatic Rust type this +/// `#[repr(C)]` struct mirrors. +/// +/// # Field-level attributes +/// +/// - `#[nullable]` — required on every pointer field whose Rust counterpart is +/// an [`Option`]. A `None` value is written as a null pointer. +/// - `#[target_name(ident)]` — name of the matching field on the Rust side +/// when it differs from the C-side name. +/// - `#[c_repr_of_convert(expr)]` — replace the generated conversion with a +/// custom expression. The owned Rust value `input: TargetType` is in scope. +/// A field marked with this attribute is also skipped by the `AsRust` +/// derive — if the reverse direction is needed, provide it with +/// `#[as_rust_extra_field(...)]` on the struct. +/// +/// # Enums +/// +/// Enums are supported only if every variant is a unit variant. #[proc_macro_derive( CReprOf, attributes(target_type, nullable, c_repr_of_convert, target_name) @@ -23,6 +60,39 @@ pub fn creprof_derive(token_stream: TokenStream) -> TokenStream { impl_creprof_macro(&ast) } +/// Derive [`AsRust`](../ffi_convert/trait.AsRust.html) for a struct or unit enum. +/// +/// Generates a non-consuming conversion that returns a freshly-allocated value +/// of the type named in `#[target_type(...)]`. C-string fields are decoded as +/// UTF-8 and copied; other pointer fields are borrowed via +/// [`RawBorrow`](../ffi_convert/trait.RawBorrow.html) and then converted with +/// their own `AsRust` impl; remaining fields go through their own `AsRust` +/// impl directly. +/// +/// The derived `AsRust` reads pointer fields under the same layout assumptions +/// as `CReprOf` / `CDrop`; deriving all three together keeps them in sync. +/// +/// # Struct-level attributes +/// +/// - `#[target_type(Path)]` — **required**. +/// - `#[as_rust_extra_field(name = expr)]` — initialise an extra field on the +/// Rust side that has no C counterpart. The attribute can be repeated; `self` +/// (the C-compatible value) is in scope inside `expr`, allowing +/// reconstruction from unrelated C-side fields. +/// +/// # Field-level attributes +/// +/// - `#[nullable]` — map a null pointer to [`None`] instead of failing. +/// - `#[target_name(ident)]` — name of the matching field on the Rust side +/// when it differs from the C-side name. +/// +/// A field annotated with `#[c_repr_of_convert(...)]` (see [`CReprOf`]) is +/// skipped by this derive; pair it with `#[as_rust_extra_field]` if the Rust +/// struct still has a matching field. +/// +/// # Enums +/// +/// Enums are supported only if every variant is a unit variant. #[proc_macro_derive( AsRust, attributes( @@ -38,12 +108,48 @@ pub fn asrust_derive(token_stream: TokenStream) -> TokenStream { impl_asrust_macro(&ast) } +/// Derive [`CDrop`](../ffi_convert/trait.CDrop.html) and (by default) [`Drop`] +/// for a struct or unit enum. +/// +/// The generated `do_drop` releases every owning pointer field by calling +/// [`RawPointerConverter::drop_raw_pointer`](../ffi_convert/trait.RawPointerConverter.html), +/// which takes the value back from the raw pointer and lets it drop. Non-pointer +/// fields are left to Rust's regular drop glue. +/// +/// Deriving [`CDrop`] assumes the struct owns its pointer fields and was initialized +/// via `CReprOf::c_repr_of`. Derive `CReprOf` and `CDrop` together to keep their +/// assumptions in sync. +/// +/// The default output also emits a `Drop` impl that calls `do_drop`, so that +/// letting a value go out of scope releases its pointer fields. A `CDrop` +/// impl without a matching `Drop` leaks every pointer field on scope exit. +/// +/// # Struct-level attributes +/// +/// - `#[no_drop_impl]` — generate only the `CDrop` impl and skip the blanket +/// `Drop` impl. Use this when you need a manual `Drop`; that manual `Drop` +/// should call `do_drop`, otherwise the pointer fields leak. +/// +/// # Field-level attributes +/// +/// - `#[nullable]` — skip the free when the pointer is null. This is the same +/// attribute that `CReprOf` and `AsRust` read on the field; annotate the +/// field once and all three derives stay in sync. #[proc_macro_derive(CDrop, attributes(no_drop_impl, nullable))] pub fn cdrop_derive(token_stream: TokenStream) -> TokenStream { let ast = syn::parse(token_stream).unwrap(); impl_cdrop_macro(&ast) } +/// Derive [`RawPointerConverter`](../ffi_convert/trait.RawPointerConverter.html) +/// for a struct. +/// +/// The derived implementation boxes `self` into `*const Self` / `*mut Self` +/// (and conversely). It is needed on any C-compatible struct that is reached +/// through a raw pointer field in another C-compatible struct, because the +/// derived [`CReprOf`] of the parent calls `into_raw_pointer()` on it. +/// +/// No helper attributes. #[proc_macro_derive(RawPointerConverter)] pub fn rawpointerconverter_derive(token_stream: TokenStream) -> TokenStream { let ast = syn::parse(token_stream).unwrap(); diff --git a/ffi-convert-extra-ctypes/src/c_array.rs b/ffi-convert-extra-ctypes/src/c_array.rs index 510d4f8..aa451e5 100644 --- a/ffi-convert-extra-ctypes/src/c_array.rs +++ b/ffi-convert-extra-ctypes/src/c_array.rs @@ -7,13 +7,26 @@ use ffi_convert::{ take_back_from_raw_pointer, take_back_from_raw_pointer_mut, }; -/// A utility type to represent arrays of the parametrized type. -/// Note that the parametrized type should have a C-compatible representation. +/// A `#[repr(C)]` mirror of [`Vec`] with impls of [`CReprOf`], [`CDrop`] +/// and [`AsRust`]. +/// +/// Layout is a `(data_ptr, size)` pair. An empty array is represented with a +/// null `data_ptr` and `size == 0`. +/// +/// When `U` is a primitive numeric type (`u8`, `i8`, `u16`, `i16`, `u32`, +/// `i32`, `f32`, or `f64`) the conversion takes a fast path: `c_repr_of` +/// reuses the input `Vec`'s buffer directly, and `as_rust` does a bulk +/// `ptr::copy` into a new `Vec`. Otherwise each element is converted +/// individually through its `CReprOf` / `AsRust` implementation. +/// +/// `CArray` owns the backing buffer and frees it via its [`Drop`] impl (by +/// way of [`CDrop`]). Do not reconstruct a `CArray` from a pointer you do not +/// own. /// /// # Example /// /// ``` -/// use ffi_convert::{CReprOf, AsRust, CDrop}; +/// use ffi_convert::{AsRust, CDrop, CReprOf}; /// use ffi_convert_extra_ctypes::CArray; /// use libc::c_char; /// @@ -21,25 +34,31 @@ use ffi_convert::{ /// pub ingredient: String, /// } /// -/// #[derive(CDrop, CReprOf, AsRust)] +/// #[derive(CReprOf, AsRust, CDrop)] /// #[target_type(PizzaTopping)] /// pub struct CPizzaTopping { -/// pub ingredient: *const c_char +/// pub ingredient: *const c_char, /// } /// /// let toppings = vec![ -/// PizzaTopping { ingredient: "Cheese".to_string() }, -/// PizzaTopping { ingredient: "Ham".to_string() } ]; +/// PizzaTopping { ingredient: "Cheese".into() }, +/// PizzaTopping { ingredient: "Ham".into() }, +/// ]; /// -/// let ctoppings = CArray::::c_repr_of(toppings); +/// // Rust -> C (the `CArray` now owns the C strings it allocated). +/// let c_toppings = CArray::::c_repr_of(toppings).unwrap(); +/// assert_eq!(c_toppings.size, 2); /// +/// // C -> Rust (deep copy; `c_toppings` stays valid). +/// let round_tripped: Vec = c_toppings.as_rust().unwrap(); +/// assert_eq!(round_tripped[0].ingredient, "Cheese"); /// ``` #[repr(C)] #[derive(Debug)] pub struct CArray { - /// Pointer to the first element of the array + /// Pointer to the first element, or null when `size == 0`. pub data_ptr: *const T, - /// Number of elements in the array + /// Number of elements in the array. pub size: usize, } diff --git a/ffi-convert-extra-ctypes/src/c_range.rs b/ffi-convert-extra-ctypes/src/c_range.rs index f21e165..5133b04 100644 --- a/ffi-convert-extra-ctypes/src/c_range.rs +++ b/ffi-convert-extra-ctypes/src/c_range.rs @@ -2,8 +2,11 @@ use std::ops::Range; use ffi_convert::{AsRust, AsRustError, CDrop, CDropError, CReprOf, CReprOfError}; -/// A utility type to represent range. -/// Note that the parametrized type T should have `CReprOf` and `AsRust` trait implemented. +/// A `#[repr(C)]` mirror of [`std::ops::Range`] with impls of [`CReprOf`], +/// [`CDrop`] and [`AsRust`]. +/// +/// Contains a plain `(start, end)` pair — no allocation involved. Use it as +/// a field type when a struct needs to expose a range through FFI. /// /// # Example /// diff --git a/ffi-convert-extra-ctypes/src/c_string_array.rs b/ffi-convert-extra-ctypes/src/c_string_array.rs index 1c6c059..4d05ba6 100644 --- a/ffi-convert-extra-ctypes/src/c_string_array.rs +++ b/ffi-convert-extra-ctypes/src/c_string_array.rs @@ -4,22 +4,42 @@ use ffi_convert::{ AsRust, AsRustError, CDrop, CDropError, CReprOf, CReprOfError, RawBorrow, RawPointerConverter, }; -/// A utility type to represent arrays of string +/// A `#[repr(C)]` mirror of `Vec` with impls of [`CReprOf`], [`CDrop`] +/// and [`AsRust`]. +/// +/// Layout is a `(data, size)` pair where `data` is a pointer to a +/// heap-allocated array of `*const c_char`, each pointing to its own +/// nul-terminated C string allocated by the Rust side. The whole structure +/// is freed via [`CDrop`](ffi_convert::CDrop) / [`Drop`]. +/// +/// Strings containing interior NUL bytes cannot be represented and will +/// cause [`CReprOf::c_repr_of`](ffi_convert::CReprOf::c_repr_of) to fail +/// with [`CReprOfError::StringContainsNullBit`](ffi_convert::CReprOfError). +/// /// # Example /// /// ``` -/// use ffi_convert::CReprOf; +/// use ffi_convert::{AsRust, CReprOf}; /// use ffi_convert_extra_ctypes::CStringArray; -/// let pizza_names = vec!["Diavola".to_string(), "Margarita".to_string(), "Regina".to_string()]; -/// let c_pizza_names = CStringArray::c_repr_of(pizza_names).expect("could not convert !"); /// +/// let pizza_names = vec![ +/// "Diavola".to_string(), +/// "Margarita".to_string(), +/// "Regina".to_string(), +/// ]; +/// let c_pizza_names = CStringArray::c_repr_of(pizza_names.clone()).unwrap(); +/// assert_eq!(c_pizza_names.size, 3); +/// +/// // Deep-copy back into owned Rust strings. +/// let round_tripped: Vec = c_pizza_names.as_rust().unwrap(); +/// assert_eq!(round_tripped, pizza_names); /// ``` #[repr(C)] #[derive(Debug, RawPointerConverter)] pub struct CStringArray { - /// Pointer to the first element of the array + /// Pointer to the first `*const c_char` element of the array. pub data: *const *const libc::c_char, - /// Number of elements in the array + /// Number of elements in the array. pub size: usize, } diff --git a/ffi-convert-extra-ctypes/src/lib.rs b/ffi-convert-extra-ctypes/src/lib.rs index 901836b..afa8ab3 100644 --- a/ffi-convert-extra-ctypes/src/lib.rs +++ b/ffi-convert-extra-ctypes/src/lib.rs @@ -1,9 +1,24 @@ -//! Extra C-compatible utility types for [`ffi_convert`]. +//! C-compatible mirrors of a few standard Rust containers, for use with +//! [`ffi-convert`](https://docs.rs/ffi-convert). //! -//! These types mirror common stdlib types ([`Vec`], [`Vec`], [`std::ops::Range`]) -//! with C-compatible representations. They are provided as a convenience but are not -//! required to use the `ffi-convert` core crate: users who prefer to define their own -//! layouts can skip this crate entirely. +//! The three types exposed here cover the containers most FFI boundaries +//! inevitably need: +//! +//! | Rust type | C-compatible mirror | +//! |------------------|--------------------------------| +//! | `Vec` | [`CArray`] | +//! | `Vec` | [`CStringArray`] | +//! | `std::ops::Range` | [`CRange`] | +//! +//! Each mirror implements [`CReprOf`](ffi_convert::CReprOf), +//! [`AsRust`](ffi_convert::AsRust), and [`CDrop`](ffi_convert::CDrop), so it +//! can be embedded in any struct you derive the conversion traits on — see +//! the top-level [`ffi-convert`](https://docs.rs/ffi-convert) documentation +//! for how the pieces fit together. +//! +//! This crate is optional: if none of these types fit your layout, depend on +//! `ffi-convert` alone and define your own `#[repr(C)]` container with the +//! conversion trait impls you need. mod c_array; mod c_range; diff --git a/ffi-convert/src/conversions.rs b/ffi-convert/src/conversions.rs index 16ec9c9..caccbea 100644 --- a/ffi-convert/src/conversions.rs +++ b/ffi-convert/src/conversions.rs @@ -4,164 +4,147 @@ use std::str::Utf8Error; use thiserror::Error; -macro_rules! impl_c_repr_of_for { - ($typ:ty) => { - impl CReprOf<$typ> for $typ { - fn c_repr_of(input: $typ) -> Result<$typ, CReprOfError> { - Ok(input) - } - } - }; - - ($from_typ:ty, $to_typ:ty) => { - impl CReprOf<$from_typ> for $to_typ { - fn c_repr_of(input: $from_typ) -> Result<$to_typ, CReprOfError> { - Ok(input as $to_typ) - } - } - }; -} - -/// implements a noop implementation of the CDrop trait for a given type. -macro_rules! impl_c_drop_for { - ($typ:ty) => { - impl CDrop for $typ { - fn do_drop(&mut self) -> Result<(), CDropError> { - Ok(()) - } - } - }; -} - -macro_rules! impl_as_rust_for { - ($typ:ty) => { - impl AsRust<$typ> for $typ { - fn as_rust(&self) -> Result<$typ, AsRustError> { - Ok(*self) - } - } - }; - - ($from_typ:ty, $to_typ:ty) => { - impl AsRust<$to_typ> for $from_typ { - fn as_rust(&self) -> Result<$to_typ, AsRustError> { - Ok(*self as $to_typ) - } - } - }; -} - -macro_rules! impl_rawpointerconverter_for { - ($typ:ty) => { - impl RawPointerConverter<$typ> for $typ { - fn into_raw_pointer(self) -> *const $typ { - convert_into_raw_pointer(self) - } - fn into_raw_pointer_mut(self) -> *mut $typ { - convert_into_raw_pointer_mut(self) - } - unsafe fn from_raw_pointer( - input: *const $typ, - ) -> Result { - unsafe { take_back_from_raw_pointer(input) } - } - unsafe fn from_raw_pointer_mut( - input: *mut $typ, - ) -> Result { - unsafe { take_back_from_raw_pointer_mut(input) } - } - } - }; -} - +/// Error returned by [`CReprOf::c_repr_of`]. #[derive(Error, Debug)] pub enum CReprOfError { + /// A Rust [`String`] contained an interior `NUL` byte and therefore could + /// not be converted to a C string. #[error("A string contains a nul bit")] StringContainsNullBit(#[from] NulError), + /// Custom error returned by a manual or overridden implementation. #[error("An error occurred during conversion to C repr; {}", .0)] Other(#[from] Box), } -/// Trait showing that the struct implementing it is a `repr(C)` compatible view of the parametrized -/// type that can be created from an value of this type. +/// Consuming conversion **from** an idiomatic Rust value **to** its +/// `#[repr(C)]` mirror. +/// +/// Implementing `CReprOf` for `T` states that `T` is a C-compatible layout +/// of the Rust value `U` and that a `T` can be built from a `U`. The resulting +/// `T` owns any heap memory it allocates, and that memory is reclaimed by the +/// corresponding [`CDrop`] implementation. +/// +/// see [Deriving the traits](crate#deriving-the-traits). pub trait CReprOf: Sized + CDrop { + /// Consume `input` and return its C-compatible representation. fn c_repr_of(input: T) -> Result; } +/// Error returned by [`CDrop::do_drop`]. #[derive(Error, Debug)] pub enum CDropError { + /// A non-nullable pointer field was found to be null while dropping. #[error("unexpected null pointer")] NullPointer(#[from] UnexpectedNullPointerError), + /// Custom error returned by a manual implementation. #[error("An error occurred while dropping C struct: {}", .0)] Other(#[from] Box), } -/// Trait showing that the C-like struct implementing it can free up its part of memory that are not -/// managed by Rust. +/// Releases heap memory referenced by a C-compatible value behind raw pointer +/// fields (typically data that was moved into a `Box` and leaked via +/// [`Box::into_raw`]). +/// +/// By default, [`#[derive(CDrop)]`](ffi_convert_derive::CDrop) emits both a +/// [`CDrop`] impl and a matching [`Drop`] impl that calls +/// [`do_drop`](CDrop::do_drop), so dropping the value through Rust's normal +/// path releases its pointer fields. `#[no_drop_impl]` suppresses only the +/// [`Drop`] impl; in that case a handwritten [`Drop`] must call `do_drop` +/// itself, otherwise the pointer fields are leaked. +/// +/// see [Deriving the traits](crate#deriving-the-traits). pub trait CDrop { + /// Release any Rust-owned memory referenced by `self`. The derived + /// [`Drop`] impl calls this and discards the result, so errors raised + /// from a normal drop are not observed. fn do_drop(&mut self) -> Result<(), CDropError>; } +/// Error returned by [`AsRust::as_rust`]. #[derive(Error, Debug)] pub enum AsRustError { + /// A non-nullable pointer field was null. #[error("unexpected null pointer")] NullPointer(#[from] UnexpectedNullPointerError), - + /// A C string field was not valid UTF-8. #[error("could not convert string as it is not UTF-8: {}", .0)] Utf8Error(#[from] Utf8Error), + /// Custom error returned by a manual implementation. #[error("An error occurred during conversion to Rust: {}", .0)] Other(#[from] Box), } -/// Trait showing that the struct implementing it is a `repr(C)` compatible view of the parametrized -/// type and that an instance of the parametrized type can be created form this struct +/// Non-consuming conversion **from** a `#[repr(C)]` value **back** to an +/// owned, idiomatic Rust value. +/// +/// `AsRust` takes `&self` and returns a freshly-allocated `U`, copying data +/// out of any pointer field by borrowing through [`RawBorrow`]. The original +/// C-compatible value is left untouched and its allocations are not freed; +/// releasing them is the caller's responsibility. +/// +/// This is the recommended entry point for values handed to Rust by C — see +/// the crate-level [Philosophy](crate#philosophy). pub trait AsRust { + /// Return a freshly-allocated Rust value equivalent to `self`. fn as_rust(&self) -> Result; } +/// Returned when a raw pointer was expected to be non-null but was null. #[derive(Error, Debug)] #[error("Could not use raw pointer: unexpected null pointer")] pub struct UnexpectedNullPointerError; -/// Trait representing the creation of a raw pointer from a struct and the recovery of said pointer. +/// Moves a Rust value onto the heap and exposes it as a raw pointer suitable +/// for crossing an FFI boundary, then takes it back on the return trip. /// -/// The `from_raw_pointer` function should be used only on pointers obtained through the -/// `into_raw_pointer` method (and is thus unsafe as we don't have any way to get insurance of that -/// from the compiler). +/// The default impls box the value and leak it via [`Box::into_raw`]. Each +/// pointer produced by `into_raw_pointer` must eventually be passed to +/// [`from_raw_pointer`](RawPointerConverter::from_raw_pointer) or +/// [`drop_raw_pointer`](RawPointerConverter::drop_raw_pointer); otherwise the +/// allocation is leaked. To read the value behind a pointer without taking +/// ownership (e.g. when the C caller retains ownership), use [`RawBorrow`] +/// — see the crate-level [Philosophy](crate#philosophy). /// -/// The `from_raw_pointer` effectively takes back ownership of the pointer. If you didn't create the -/// pointer yourself, please use the `as_ref` method on the raw pointer to borrow it +/// The `from_raw_pointer` family is `unsafe` because the compiler cannot +/// verify that the pointer originated from `into_raw_pointer`. Passing the +/// same pointer twice frees the same allocation twice. pub trait RawPointerConverter: Sized { - /// Creates a raw pointer from the value and leaks it, you should use [`Self::from_raw_pointer`] - /// or [`Self::drop_raw_pointer`] to free the value when you're done with it. + /// Leak the value behind a raw pointer. Pair with [`Self::from_raw_pointer`] + /// or [`Self::drop_raw_pointer`] to release the allocation. fn into_raw_pointer(self) -> *const T; - /// Creates a mutable raw pointer from the value and leaks it, you should use - /// [`Self::from_raw_pointer_mut`] or [`Self::drop_raw_pointer_mut`] to free the value when - /// you're done with it. + /// Leak the value behind a mutable raw pointer. Pair with + /// [`Self::from_raw_pointer_mut`] or [`Self::drop_raw_pointer_mut`] to + /// release the allocation. fn into_raw_pointer_mut(self) -> *mut T; - /// Takes back control of a raw pointer created by [`Self::into_raw_pointer`]. + /// Take back ownership of a raw pointer previously produced by + /// [`Self::into_raw_pointer`]. Returns [`UnexpectedNullPointerError`] if + /// `input` is null. /// # Safety - /// This method is unsafe because passing it a pointer that was not created by - /// [`Self::into_raw_pointer`] can lead to memory problems. Also note that passing the same pointer - /// twice to this function will probably result in a double free + /// `input` must have been produced by [`Self::into_raw_pointer`] and must + /// not be used afterwards. Passing the same pointer twice frees the same + /// allocation twice. unsafe fn from_raw_pointer(input: *const T) -> Result; - /// Takes back control of a raw pointer created by [`Self::into_raw_pointer_mut`]. + /// Take back ownership of a raw pointer previously produced by + /// [`Self::into_raw_pointer_mut`]. Returns [`UnexpectedNullPointerError`] + /// if `input` is null. /// # Safety - /// This method is unsafe because passing it a pointer that was not created by - /// [`Self::into_raw_pointer_mut`] can lead to memory problems. Also note that passing the same - /// pointer twice to this function will probably result in a double free + /// `input` must have been produced by [`Self::into_raw_pointer_mut`] and + /// must not be used afterwards. Passing the same pointer twice frees the + /// same allocation twice. unsafe fn from_raw_pointer_mut(input: *mut T) -> Result; - /// Takes back control of a raw pointer created by [`Self::into_raw_pointer`] and drop it. + /// Take back ownership of a pointer produced by [`Self::into_raw_pointer`] + /// and drop the value. /// # Safety - /// This method is unsafe for the same reasons as [`Self::from_raw_pointer`] + /// Same requirements as [`Self::from_raw_pointer`]. unsafe fn drop_raw_pointer(input: *const T) -> Result<(), UnexpectedNullPointerError> { unsafe { Self::from_raw_pointer(input) }.map(|_| ()) } - /// Takes back control of a raw pointer created by [`Self::into_raw_pointer_mut`] and drops it. + /// Take back ownership of a pointer produced by [`Self::into_raw_pointer_mut`] + /// and drop the value. /// # Safety - /// This method is unsafe for the same reasons a [`Self::from_raw_pointer_mut`] + /// Same requirements as [`Self::from_raw_pointer_mut`]. unsafe fn drop_raw_pointer_mut(input: *mut T) -> Result<(), UnexpectedNullPointerError> { unsafe { Self::from_raw_pointer_mut(input) }.map(|_| ()) } @@ -195,34 +178,41 @@ pub unsafe fn take_back_from_raw_pointer_mut( } } -/// Trait to create borrowed references to type T, from a raw pointer to a T. Note that this is -/// implemented for all types. +/// Turn a `*const T` into a borrowed `&T` without taking ownership. +/// +/// Use this when the pointer was handed to you by C and the C side retains +/// ownership of the allocation — see the crate-level +/// [Philosophy](crate#philosophy). A blanket impl `impl RawBorrow for T` +/// covers every type; [`std::ffi::CStr`] additionally implements +/// `RawBorrow`. pub trait RawBorrow { - /// Get a reference on the value behind the pointer or return an error if the pointer is `null`. + /// Borrow the value behind `input`, or return + /// [`UnexpectedNullPointerError`] if it is null. + /// /// # Safety - /// As this is using `*const T::as_ref()` this is unsafe for exactly the same reasons. + /// Thin wrapper around `<*const T>::as_ref` with the same requirements: + /// `input` must point to a valid, properly aligned `T` that lives for at + /// least `'a`. unsafe fn raw_borrow<'a>(input: *const T) -> Result<&'a Self, UnexpectedNullPointerError>; } -/// Trait to create mutable borrowed references to type T, from a raw pointer to a T. Note that this -/// is implemented for all types. +/// Mutable counterpart of [`RawBorrow`]. pub trait RawBorrowMut { - /// Get a mutable reference on the value behind the pointer or return an error if the pointer is - /// `null`. + /// Borrow the value behind `input` mutably, or return + /// [`UnexpectedNullPointerError`] if it is null. + /// /// # Safety - /// As this is using `*mut T:as_ref()` this is unsafe for exactly the same reasons. + /// Thin wrapper around `<*mut T>::as_mut` with the same requirements. unsafe fn raw_borrow_mut<'a>(input: *mut T) -> Result<&'a mut Self, UnexpectedNullPointerError>; } -/// Trait that allows obtaining a borrowed reference to a type T from a raw pointer to T impl RawBorrow for T { unsafe fn raw_borrow<'a>(input: *const T) -> Result<&'a Self, UnexpectedNullPointerError> { unsafe { input.as_ref() }.ok_or(UnexpectedNullPointerError) } } -/// Trait that allows obtaining a mutable borrowed reference to a type T from a raw pointer to T impl RawBorrowMut for T { unsafe fn raw_borrow_mut<'a>( input: *mut T, @@ -295,19 +285,47 @@ impl RawBorrow for std::ffi::CStr { } } -impl_c_drop_for!(usize); -impl_c_drop_for!(i8); -impl_c_drop_for!(u8); -impl_c_drop_for!(i16); -impl_c_drop_for!(u16); -impl_c_drop_for!(i32); -impl_c_drop_for!(u32); -impl_c_drop_for!(i64); -impl_c_drop_for!(u64); -impl_c_drop_for!(f32); -impl_c_drop_for!(f64); -impl_c_drop_for!(bool); -impl_c_drop_for!(std::ffi::CString); +macro_rules! impl_noop_c_drop_for { + ($typ:ty) => { + impl CDrop for $typ { + fn do_drop(&mut self) -> Result<(), CDropError> { + Ok(()) + } + } + }; +} + +impl_noop_c_drop_for!(usize); +impl_noop_c_drop_for!(i8); +impl_noop_c_drop_for!(u8); +impl_noop_c_drop_for!(i16); +impl_noop_c_drop_for!(u16); +impl_noop_c_drop_for!(i32); +impl_noop_c_drop_for!(u32); +impl_noop_c_drop_for!(i64); +impl_noop_c_drop_for!(u64); +impl_noop_c_drop_for!(f32); +impl_noop_c_drop_for!(f64); +impl_noop_c_drop_for!(bool); +impl_noop_c_drop_for!(std::ffi::CString); + +macro_rules! impl_c_repr_of_for { + ($typ:ty) => { + impl CReprOf<$typ> for $typ { + fn c_repr_of(input: $typ) -> Result<$typ, CReprOfError> { + Ok(input) + } + } + }; + + ($from_typ:ty, $to_typ:ty) => { + impl CReprOf<$from_typ> for $to_typ { + fn c_repr_of(input: $from_typ) -> Result<$to_typ, CReprOfError> { + Ok(input as $to_typ) + } + } + }; +} impl_c_repr_of_for!(usize); impl_c_repr_of_for!(i8); @@ -330,6 +348,24 @@ impl CReprOf for std::ffi::CString { } } +macro_rules! impl_as_rust_for { + ($typ:ty) => { + impl AsRust<$typ> for $typ { + fn as_rust(&self) -> Result<$typ, AsRustError> { + Ok(*self) + } + } + }; + + ($from_typ:ty, $to_typ:ty) => { + impl AsRust<$to_typ> for $from_typ { + fn as_rust(&self) -> Result<$to_typ, AsRustError> { + Ok(*self as $to_typ) + } + } + }; +} + impl_as_rust_for!(usize); impl_as_rust_for!(i8); impl_as_rust_for!(u8); @@ -351,6 +387,29 @@ impl AsRust for std::ffi::CStr { } } +macro_rules! impl_rawpointerconverter_for { + ($typ:ty) => { + impl RawPointerConverter<$typ> for $typ { + fn into_raw_pointer(self) -> *const $typ { + convert_into_raw_pointer(self) + } + fn into_raw_pointer_mut(self) -> *mut $typ { + convert_into_raw_pointer_mut(self) + } + unsafe fn from_raw_pointer( + input: *const $typ, + ) -> Result { + unsafe { take_back_from_raw_pointer(input) } + } + unsafe fn from_raw_pointer_mut( + input: *mut $typ, + ) -> Result { + unsafe { take_back_from_raw_pointer_mut(input) } + } + } + }; +} + impl_rawpointerconverter_for!(usize); impl_rawpointerconverter_for!(i16); impl_rawpointerconverter_for!(u16); diff --git a/ffi-convert/src/lib.rs b/ffi-convert/src/lib.rs index 9555b6d..560d4fb 100644 --- a/ffi-convert/src/lib.rs +++ b/ffi-convert/src/lib.rs @@ -1,71 +1,77 @@ -//! A collection of utilities (traits, data structures, conversion functions, etc ...) to ease conversion between Rust and C-compatible data structures. +//! Traits and helpers to convert between idiomatic Rust values and C-compatible +//! representations when crossing an FFI boundary. +//! +//! The crate is built around two conversion traits, [`CReprOf`] and [`AsRust`], +//! and two supporting traits, [`CDrop`] and [`RawPointerConverter`]. Derive +//! macros for all four are provided by the companion +//! [`ffi-convert-derive`](https://docs.rs/ffi-convert-derive) crate and +//! re-exported here. +//! +//! Common containers (arrays, string arrays, ranges) live in the separate +//! [`ffi-convert-extra-ctypes`](https://docs.rs/ffi-convert-extra-ctypes) +//! crate and can be pulled in on demand. +//! +//! # Philosophy +//! +//! `ffi-convert`'s memory-management model makes as few assumptions as +//! possible about how the C side allocates, holds, or frees memory. +//! +//! Two traits cover the two directions across the FFI boundary: +//! +//! - **Incoming from C** — [`AsRust`] takes a `&CFoo` and returns an owned +//! `Foo` built by deep-copying every field. It is a defensive copy: once +//! `as_rust` returns, the resulting Rust value does not reference any +//! C-owned memory, and nothing else in the crate reads from the original +//! pointer afterwards. The C caller is free to keep, reuse, or release +//! the pointer however its own rules require. +//! - **Outgoing to C** — [`CReprOf`] consumes a `Foo` and produces a `CFoo` +//! that owns any heap memory its pointer fields reference. The `CFoo` is +//! then handed to C as a raw pointer; to release everything, C sends the +//! pointer back to Rust through a `free`-style FFI function that lets the +//! value drop (releasing its pointer fields via [`CDrop`]). +//! +//! ```text +//! CPizza::c_repr_of(pizza) +//! ┌───────────────────────────────┐ +//! │ ▼ +//! ┌──────────┐ ┌──────────┐ +//! │ Pizza │ │ CPizza │ +//! │ (Rust) │ │ (C) │ +//! └──────────┘ └──────────┘ +//! ▲ │ +//! └───────────────────────────────┘ +//! c_pizza.as_rust() +//! ``` //! -//! Through two **conversion traits**, [`CReprOf`] and [`AsRust`], this crate provides a framework to convert idiomatic Rust structs to C-compatible structs that can pass through an FFI boundary, and conversely. -//! They ensure that the developer uses best practices when performing the conversion in both directions (ownership-wise). +//! # Quick example //! -//! The crate also provides a collection of useful utility functions and traits to perform conversions of types. -//! It goes hand in hand with the `ffi-convert-derive` crate as it provides an **automatic derivation** of the [`CReprOf`] and [`AsRust`] trait. +//! Define the Rust type you want to expose, then define a `#[repr(C)]` mirror +//! and derive the conversion traits. The mirror's fields use C-compatible +//! types (see [the mapping table](#type-mapping)). //! -//! # Usage -//! When dealing with an FFI frontier, the general philosophy of the crate is : -//! - When receiving pointers to structs created by C code, the struct is immediately converted to an owned (via a copy), idiomatic Rust struct through the use of the [`AsRust`] trait. -//! - To send an idiomatic, owned Rust struct to C code, the struct is converted to C-compatible representation using the [`CReprOf`] trait. +//! ``` +//! use ffi_convert::{AsRust, CDrop, CReprOf, RawBorrow, RawPointerConverter}; +//! use libc::{c_char, c_float}; //! -//! ## Example +//! pub struct Sauce { +//! pub spiciness: f32, +//! } +//! +//! #[repr(C)] +//! #[derive(CReprOf, AsRust, CDrop, RawPointerConverter)] +//! #[target_type(Sauce)] +//! pub struct CSauce { +//! pub spiciness: c_float, +//! } //! -//! We want to be able to convert a **`Pizza`** Rust struct that has an idiomatic representation to a **`CPizza`** Rust struct that has a C-compatible representation in memory. -//! We start by defining the fields of the `Pizza` struct : -//! ``` -//! # struct Sauce {}; //! pub struct Pizza { //! pub name: String, //! pub base: Option, //! pub weight: f32, //! } -//!``` //! -//! We then create the C-compatible struct by [mapping](#types-representations-mapping) idiomatic Rust types to C-compatible types : -//! ``` -//! # struct CSauce {}; //! #[repr(C)] -//! pub struct CPizza { -//! pub name: *const libc::c_char, -//! pub base: *const CSauce, -//! pub weight: libc::c_float, -//! } -//! ``` -//! -//! This crate provides two traits that are useful for converting between Pizza to CPizza and conversely. -//! -//! ```ignore -//! CPizza::c_repr_of(pizza) -//! <=================| -//! -//! CPizza Pizza -//! -//! |=================> -//! cpizza.as_rust() -//! -//! ``` -//! Instead of manually writing the body of the conversion traits, we can derive them : -//! -//! ``` -//! # use ffi_convert::{CReprOf, AsRust, CDrop, RawPointerConverter}; -//! # use ffi_convert::RawBorrow; -//! # struct Pizza { -//! # name: String, -//! # base: Option, -//! # weight: f32 -//! # }; -//! use libc::{c_char, c_float}; -//! -//! struct Sauce {}; //! #[derive(CReprOf, AsRust, CDrop, RawPointerConverter)] -//! #[target_type(Sauce)] -//! struct CSauce {}; -//! -//! #[repr(C)] -//! #[derive(CReprOf, AsRust, CDrop)] //! #[target_type(Pizza)] //! pub struct CPizza { //! pub name: *const c_char, @@ -75,132 +81,170 @@ //! } //! ``` //! -//! You may have noticed that you have to derive two traits : the CDrop trait and the RawPointerConverter trait. -//! -//! The CDrop trait needs to be implemented on every C-compatible struct that require manual resource management. -//! The release of those resources should be done in the drop method of the CDrop trait. -//! -//! The RawPointerConverter trait is implemented to perform the conversion of a C-like struct to a raw-pointer to this C-like structure (and conversely). -//! Here, it is used behind the scene to convert a `CSauce` struct to a pointer to a raw pointer to CSause struct : `*const CSauce` -//! (needed behind the scenes when the [`CReprOf`] trait is derived for `CPizza`). +//! Two things to notice: //! -//! You can now pass the `CPizza` struct through your FFI boundary ! +//! - `CSauce` derives [`RawPointerConverter`] because `CPizza::base` stores a +//! `*const CSauce`; `CPizza` derives it too so it can itself be handed to C +//! as a `*const CPizza`. In both cases the derived [`CReprOf`] turns a value +//! into a raw pointer via `into_raw_pointer`. +//! - `CPizza::base` carries `#[nullable]` because the Rust field is +//! `Option`. The attribute tells the derives to map `None` to a null +//! pointer on the way out and a null pointer to `None` on the way back. //! - -//! ## Types representations mapping -//! -//! `T : CReprOf + AsRust` -//! -//! The rows below marked as coming from the companion -//! [`ffi-convert-extra-ctypes`](https://docs.rs/ffi-convert-extra-ctypes) crate are -//! optional: they cover common container shapes but users are free to define their own -//! C-compatible layouts and skip that crate entirely. -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//!
C typeRust typeC-compatible Rust typeProvided by
const char*String*const libc::c_charffi-convert
const T*U*const Tffi-convert
T*U*mut Tffi-convert
TUTffi-convert
const T*Option<U>*const T (with #[nullable] field annotation)ffi-convert
CArrayTVec<U>CArray<T>ffi-convert-extra-ctypes
CStringArrayVec<String>CStringArrayffi-convert-extra-ctypes
CRangeTRange<U>CRange<T>ffi-convert-extra-ctypes
+//! With the derives in place, let's write an FFI wrapper with three small functions — +//! one to read a C-owned value, one to hand a Rust value to C, and one to free +//! it: //! - -//! ## The CReprOf trait - -//! The `CReprOf` trait allows to create a C-compatible representation of the reciprocal idiomatic Rust struct by consuming the latter. - //! ``` -//! # use ffi_convert::{CReprOfError, CDrop}; -//! pub trait CReprOf: Sized + CDrop { -//! fn c_repr_of(input: T) -> Result; +//! # use ffi_convert::{AsRust, CDrop, CReprOf, RawBorrow, RawPointerConverter}; +//! # use libc::{c_char, c_float}; +//! # pub struct Sauce { pub spiciness: f32 } +//! # #[repr(C)] +//! # #[derive(CReprOf, AsRust, CDrop, RawPointerConverter)] +//! # #[target_type(Sauce)] +//! # pub struct CSauce { pub spiciness: c_float } +//! # pub struct Pizza { +//! # pub name: String, +//! # pub base: Option, +//! # pub weight: f32, +//! # } +//! # #[repr(C)] +//! # #[derive(CReprOf, AsRust, CDrop, RawPointerConverter)] +//! # #[target_type(Pizza)] +//! # pub struct CPizza { +//! # pub name: *const c_char, +//! # #[nullable] +//! # pub base: *const CSauce, +//! # pub weight: c_float, +//! # } +//! // Read a CPizza handed to us by C: deep-copy its contents into an owned +//! // Rust `Pizza`, then run whatever logic we need. The original pointer is +//! // untouched; C keeps ownership of it. +//! #[unsafe(no_mangle)] +//! pub unsafe extern "C" fn inspect_pizza(c_pizza: *const CPizza) { +//! let c_pizza = unsafe { CPizza::raw_borrow(c_pizza) } +//! .expect("c_pizza must not be null"); +//! let pizza: Pizza = c_pizza.as_rust().expect("invalid CPizza contents"); +//! println!("{} ({}g)", pizza.name, pizza.weight); //! } -//! ``` - -//! This shows that the struct implementing it is a `repr(C)` compatible view of the parametrized -//! type and can be created from an object of this type. - -//! ## The AsRust trait - -//! > When trying to convert a `repr(C)` struct that originated from C, the philosophy is to immediately convert -//! > the struct to an **owned** idiomatic representation of the struct via the AsRust trait. - //! -//! The [`AsRust`] trait allows to create an idiomatic Rust struct from a C-compatible struct : - -//! ``` -//! # use ffi_convert::{AsRustError, CDrop}; -//! pub trait AsRust { -//! fn as_rust(&self) -> Result; +//! // Build a Rust `Pizza`, convert it to `CPizza`, and hand C a raw pointer +//! // via [`RawPointerConverter::into_raw_pointer`]. The caller must +//! // eventually invoke `free_pizza` to release the allocation. +//! #[unsafe(no_mangle)] +//! pub extern "C" fn make_pizza() -> *const CPizza { +//! let pizza = Pizza { +//! name: "Margarita".to_owned(), +//! base: Some(Sauce { spiciness: 1.5 }), +//! weight: 450.0, +//! }; +//! CPizza::c_repr_of(pizza) +//! .expect("pizza name contains an interior NUL byte") +//! .into_raw_pointer() +//! } +//! +//! // Reclaim a pointer produced by `make_pizza`. +//! // [`RawPointerConverter::drop_raw_pointer`] takes ownership back and +//! // drops the value, releasing every inner pointer field via [`CDrop`]. +//! #[unsafe(no_mangle)] +//! pub unsafe extern "C" fn free_pizza(c_pizza: *const CPizza) { +//! let _ = unsafe { CPizza::drop_raw_pointer(c_pizza) }; //! } //! ``` - -//! This shows that the struct implementing it is a `repr(C)` compatible view of the parametrized -//! type and that an instance of the parametrized type can be created from this struct. - -//! ## The CDrop trait - -//! A Trait showing that the `repr(C)` compatible view implementing it can free up its part of memory that are not -//! managed by Rust drop mechanism. - -//! ## The RawPointerConverter trait - -//! This trait completes the conversion traits toolbox provided by this crate : It expresses the -//! conversion of a C-like struct to a raw pointer to this struct and conversely. //! -//! This conversion trait comes in handy for C-like struct that have fields that points to other structs. +//! # Type mapping +//! +//! `T: CReprOf + AsRust` — in the table below, `T` is the C-compatible +//! Rust type and `U` is the idiomatic Rust type. +//! +//! | C type | Rust type (`U`) | C-compatible Rust type (`T`) | Provided by | +//! |------------------------|-------------------|---------------------------------------------------------------------------------------------------------------------|------------------------------| +//! | any scalar (`int`, …) | same scalar | same scalar | `ffi-convert` | +//! | `const char*` | `String` | `*const libc::c_char` | `ffi-convert` | +//! | `const T*` | `U` | `*const T` | `ffi-convert` | +//! | `T*` | `U` | `*mut T` | `ffi-convert` | +//! | `const T*` (nullable) | `Option` | `*const T` with `#[nullable]` | `ffi-convert` | +//! | `T[N]` | `[U; N]` | `[T; N]` | `ffi-convert` | +//! | `CArrayT` | `Vec` | [`CArray`](https://docs.rs/ffi-convert-extra-ctypes/latest/ffi_convert_extra_ctypes/struct.CArray.html) | `ffi-convert-extra-ctypes` | +//! | `CStringArray` | `Vec` | [`CStringArray`](https://docs.rs/ffi-convert-extra-ctypes/latest/ffi_convert_extra_ctypes/struct.CStringArray.html) | `ffi-convert-extra-ctypes` | +//! | `CRangeT` | `Range` | [`CRange`](https://docs.rs/ffi-convert-extra-ctypes/latest/ffi_convert_extra_ctypes/struct.CRange.html) | `ffi-convert-extra-ctypes` | +//! +//! The derives accept both `*const T` and `*mut T` for any pointer row. +//! +//! # Traits at a glance +//! +//! | Trait | Direction | Purpose | +//! |--------------------------|----------------------|-------------------------------------------------------------------------------------------------------| +//! | [`CReprOf`] | Rust → C | Consume an idiomatic Rust value and produce its C-compatible twin. | +//! | [`AsRust`] | C → Rust | Produce an owned Rust value from a borrowed C-compatible value. | +//! | [`CDrop`] | cleanup | Free heap data owned by a C-compatible struct. | +//! | [`RawPointerConverter`] | pointer boxing | Box a value into `*const T` / `*mut T` and take it back. | +//! | [`RawBorrow`] | pointer borrowing | Borrow `&T` from a raw pointer without taking ownership. Returns an error if the pointer is null. | +//! | [`RawBorrowMut`] | pointer borrowing | Borrow `&mut T` from a raw pointer without taking ownership. Returns an error if the pointer is null. | +//! +//! [`CReprOf`], [`AsRust`], [`CDrop`], and [`RawPointerConverter`] all have +//! derive macros. +//! +//! # Deriving the traits +//! +//! The derives are the intended way to use the crate. Typical derive +//! combinations on a `#[repr(C)]` type are: +//! +//! - `#[derive(CReprOf, CDrop)]` for a type created in Rust and read from C +//! - `#[derive(AsRust)]` for a type created in C and read in Rust +//! - `#[derive(AsRust, CReprOf, CDrop)]` for a type created and read in C and Rust +//! +//! Deriving `CDrop` and `CReprOf` together is recommended: `CDrop` assumes raw +//! pointers were initialized the way the `CReprOf` derive initializes them. +//! +//! The derives expect: +//! +//! - `#[target_type(Path)]` on every struct or enum that derives [`CReprOf`] +//! or [`AsRust`], pointing at the idiomatic Rust type being mirrored. +//! - `#[nullable]` on every pointer field whose Rust counterpart is an +//! [`Option`]. The attribute is shared by all three derives: [`CReprOf`] +//! reads it to emit a null for `None`, [`AsRust`] to return `None` on a +//! null pointer, and [`CDrop`] to skip the free on null. A mismatch +//! between the Rust-side `Option` and the C-side `#[nullable]` is a +//! compile error. +//! - [`RawPointerConverter`] to be implemented on any nested C-compatible +//! struct reached through a pointer field, usually by +//! `#[derive(RawPointerConverter)]`. +//! +//! The available attributes are: +//! +//! | Attribute | Applies to | Used by | Purpose | +//! |------------------------------------------|-------------------------|-----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------| +//! | `#[target_type(Path)]` | struct / enum | `CReprOf`, `AsRust` | The idiomatic Rust type this C-compatible type mirrors. | +//! | `#[no_drop_impl]` | struct / enum | `CDrop` | Only implement [`CDrop`]; skip the blanket [`Drop`] impl so you can write one manually. | +//! | `#[as_rust_extra_field(name = expr)]` | struct | `AsRust` | Initialize an extra field on the Rust side that has no C counterpart. Repeatable; `self` (the C-side value) is in scope inside `expr`. | +//! | `#[nullable]` | pointer field | `CReprOf`, `AsRust`, `CDrop`| Treat a `*const T` / `*mut T` as `Option<…>`. Required for every optional pointer field. | +//! | `#[target_name(ident)]` | field | `CReprOf`, `AsRust` | Name of the corresponding field on the Rust side when it differs from the C-side name. | +//! | `#[c_repr_of_convert(expr)]` | field | `CReprOf`, `AsRust` | Override the `CReprOf` conversion with a custom expression. The owned `input: TargetType` is in scope. The field is also skipped by `AsRust`. | +//! +//! ## Constraints +//! +//! - **C strings**: a field is recognized as a C string only when the +//! pointee's type name is literally `c_char` — `*const libc::c_char`, +//! `*mut libc::c_char`, and `*const c_char` all qualify. A `type` alias +//! for `c_char` is not recognized. +//! - **Multi-level pointer fields** (such as `*const *const CFoo`) are +//! accepted by the [`AsRust`] derive only when the field is also +//! `#[nullable]`. +//! - **Enums with data**:not supported. the derives accept enums only when +//! every variant is a unit variant. +//! +//! # Interop checklist +//! +//! A typical FFI-exposed function follows this pattern: +//! +//! 1. Receive a `*const CInput` from C and convert it with [`AsRust`], or +//! borrow it with [`RawBorrow`] if the C side keeps ownership. +//! 2. Run the Rust logic. +//! 3. Build a `COutput` with [`CReprOf`] and return it to C via +//! [`RawPointerConverter::into_raw_pointer`]. +//! 4. Expose a `free`-style function that takes the pointer back with +//! [`RawPointerConverter::from_raw_pointer`] and lets the value drop. pub use ffi_convert_derive::*;