From a057363fa48900fcc12c088cc42b55837d313e91 Mon Sep 17 00:00:00 2001 From: Thibaut Lorrain Date: Fri, 17 Apr 2026 00:38:39 +0200 Subject: [PATCH 1/5] rewrite the documentation --- README.md | 52 ++- ffi-convert-derive/src/lib.rs | 126 +++++- ffi-convert-extra-ctypes/src/c_array.rs | 37 +- ffi-convert-extra-ctypes/src/c_range.rs | 7 +- .../src/c_string_array.rs | 32 +- ffi-convert-extra-ctypes/src/lib.rs | 25 +- ffi-convert/src/conversions.rs | 106 +++-- ffi-convert/src/lib.rs | 388 ++++++++++-------- 8 files changed, 548 insertions(+), 225 deletions(-) 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..f6e1f36 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,44 @@ 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`. String fields (`*const c_char`) are +/// re-allocated as C strings, pointer fields are boxed via +/// [`RawPointerConverter::into_raw_pointer`](../ffi_convert/trait.RawPointerConverter.html), +/// and scalar fields are passed through unchanged. +/// +/// `CReprOf` and [`CDrop`](../ffi_convert/trait.CDrop.html) share an +/// ownership contract: each pointer field produced by `c_repr_of` is a +/// `Box::into_raw`'d value that the derived `CDrop` reclaims with +/// `Box::from_raw`. The recommended shape is +/// `#[derive(CReprOf, AsRust, CDrop)]` as a set — deriving `CReprOf` while +/// hand-writing `CDrop` (or the reverse) breaks the contract and causes +/// double frees or leaks. For per-field customization, prefer +/// `#[c_repr_of_convert]` / `#[as_rust_extra_field]` over hand-writing one +/// of the impls. +/// +/// # 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 +70,41 @@ 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 strings are decoded as UTF-8 +/// and copied; pointer fields are borrowed via +/// [`RawBorrow`](../ffi_convert/trait.RawBorrow.html) and then recursively +/// converted with `AsRust`. +/// +/// The derived `AsRust` reads pointer fields under the same ownership +/// contract that `CReprOf` / `CDrop` use (each pointer is a +/// `Box::into_raw`'d value). If you derive any of the three, derive all of +/// them; mixing a derived `AsRust` with a hand-written `CReprOf` that +/// allocates pointer fields differently is undefined behavior. +/// +/// # 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 +120,54 @@ 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` takes back ownership of every owning pointer field +/// with [`RawPointerConverter::from_raw_pointer`](../ffi_convert/trait.RawPointerConverter.html) +/// and lets it drop. Scalar and array fields are left to Rust's regular drop +/// glue. +/// +/// Deriving [`CDrop`] assumes the struct owns its pointer fields — typically +/// because it was built via `CReprOf::c_repr_of`. Dropping a value built from +/// borrowed or shared pointers is undefined behavior. +/// +/// `CDrop` and [`CReprOf`](../ffi_convert/trait.CReprOf.html) must agree on +/// how pointer fields are allocated: derive both together, or write both by +/// hand. Mixing a derived `CDrop` with a hand-written `CReprOf` (or the +/// reverse) breaks the contract and leads to double frees or leaks. +/// +/// The default output is the intended shape: both impls should coexist, so +/// that letting a value go out of scope actually frees the memory. A +/// `CDrop` impl without a matching `Drop` silently leaks every pointer +/// field. +/// +/// # 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` +/// must call `do_drop`, otherwise the struct leaks. +/// +/// # 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 agree. #[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..f914873 100644 --- a/ffi-convert-extra-ctypes/src/c_array.rs +++ b/ffi-convert-extra-ctypes/src/c_array.rs @@ -7,13 +7,24 @@ 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`] where `T: CReprOf + 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 `memcpy`-style path, +/// reusing the same buffer for both sides. 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 +32,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..5897711 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`] where +/// `T: CReprOf + 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..88f33de 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`: a contiguous array of +/// nul-terminated C strings. +/// +/// Layout is a `(data, size)` pair where `data` is a pointer to a +/// heap-allocated array of `*const c_char`. Each element is itself an owned +/// 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..8168956 100644 --- a/ffi-convert/src/conversions.rs +++ b/ffi-convert/src/conversions.rs @@ -74,63 +74,115 @@ macro_rules! impl_rawpointerconverter_for { }; } +/// 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 +/// implementation owns any heap memory it allocates, and that memory is +/// reclaimed by the corresponding [`CDrop`] implementation. +/// +/// `CReprOf` and [`CDrop`] share an ownership contract — each allocation +/// `c_repr_of` performs must be freeable by `do_drop`, and vice versa. The +/// derives enforce that contract by generating both sides together. If one +/// is derived, the other must be too; mixing a derived impl with a +/// hand-written one is a recipe for double frees or leaks. 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 any heap memory owned by a C-compatible value that is not managed +/// by Rust's regular `Drop` mechanism (typically `Box`-allocated data behind +/// raw pointer fields). +/// +/// The [`#[derive(CDrop)]`](ffi_convert_derive::CDrop) macro emits both a +/// [`CDrop`] and a matching [`Drop`] impl that calls +/// [`do_drop`](CDrop::do_drop). The two should always ship together — +/// a [`CDrop`] impl by itself does nothing until something calls `do_drop`, +/// so leaving the value to Rust's regular `drop` leaks every pointer field +/// it owns. Use `#[no_drop_impl]` only when you need to write [`Drop`] +/// yourself, and make sure that manual [`Drop`] calls `do_drop`. +/// +/// [`CDrop`] and [`CReprOf`] share an ownership contract: the derived +/// [`CDrop`] assumes every pointer field was produced by the derived +/// [`CReprOf`] (i.e. via `Box::into_raw`). Mixing a derived [`CDrop`] with +/// a hand-written [`CReprOf`] — or vice versa — is how you get double +/// frees or leaks. Derive both or write both. pub trait CDrop { + /// Release any Rust-owned memory referenced by `self`. Typically called + /// from the generated [`Drop`] implementation; in that case errors are + /// silently ignored. 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`, performing a +/// deep copy of any pointer field it owns. After the call the original +/// C-compatible struct is still valid — only the C side is expected to free +/// it. pub trait AsRust { + /// Return a freshly-allocated Rust value equivalent to `self`. fn as_rust(&self) -> Result; } +/// Returned whenever a raw pointer was expected to be non-null but was. #[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. +/// Boxes a Rust value into a raw pointer suitable for crossing an FFI +/// boundary, and takes it back. /// -/// 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). +/// `into_raw_pointer` leaks the value (via [`Box::into_raw`]) and must be +/// paired with a later [`from_raw_pointer`](RawPointerConverter::from_raw_pointer) +/// or [`drop_raw_pointer`](RawPointerConverter::drop_raw_pointer) call to +/// avoid a leak. If you only need to read the value behind the pointer +/// without taking ownership — because the C caller still owns the allocation +/// — use [`RawBorrow`] instead. /// -/// 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 was actually produced by `into_raw_pointer`. Calling it +/// twice on the same pointer is a double free. 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. @@ -195,34 +247,40 @@ 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. Blanket-implemented for every `T`; also +/// implemented for [`std::ffi::CStr`] over `*const libc::c_char`. 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. + /// This is a thin wrapper around `<*const T>::as_ref` and is unsafe for + /// the same reasons: `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. + /// This is a thin wrapper around `<*mut T>::as_mut` and is unsafe for the + /// same reasons. 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, diff --git a/ffi-convert/src/lib.rs b/ffi-convert/src/lib.rs index 9555b6d..43ff681 100644 --- a/ffi-convert/src/lib.rs +++ b/ffi-convert/src/lib.rs @@ -1,68 +1,57 @@ -//! 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. //! -//! 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). +//! The crate is built around two conversion traits, +//! [`CReprOf`] and [`AsRust`], and two supporting traits, +//! [`CDrop`] and [`RawPointerConverter`]. Together they form a small framework +//! that makes it hard to get ownership wrong while moving data across the +//! boundary in either direction. Deriving them (via the companion +//! [`ffi-convert-derive`](https://docs.rs/ffi-convert-derive) crate, re-exported +//! here) removes most of the boilerplate. //! -//! 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. +//! Common containers (arrays, string arrays, ranges) live in the separate +//! [`ffi-convert-extra-ctypes`](https://docs.rs/ffi-convert-extra-ctypes) crate +//! so that users who want to define their own C layouts can skip them. //! -//! # 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. +//! # Philosophy //! -//! ## Example +//! When a pointer crosses the FFI boundary, decide *once* which side owns it: //! -//! 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, -//! } -//! ``` +//! - **Incoming from C**: immediately convert to an owned, idiomatic Rust value +//! with [`AsRust`]. After that call the Rust value is fully self-contained +//! and the C-side struct can be dropped or handed back to the caller. +//! - **Outgoing to C**: build a C-compatible struct from an owned Rust value +//! with [`CReprOf`]. That struct now owns whatever heap memory it points to +//! and must eventually be freed via [`CDrop`] (typically by sending the +//! pointer back to Rust through a `free`-style FFI function that calls +//! [`RawPointerConverter::from_raw_pointer`]). //! -//! This crate provides two traits that are useful for converting between Pizza to CPizza and conversely. +//! # Quick example //! -//! ```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 : +//! Define the Rust type you want to expose, then define its `#[repr(C)]` +//! mirror and derive the conversion traits. The mirror's fields use +//! C-compatible types (see [the mapping table](#type-mapping)). //! //! ``` -//! # use ffi_convert::{CReprOf, AsRust, CDrop, RawPointerConverter}; -//! # use ffi_convert::RawBorrow; -//! # struct Pizza { -//! # name: String, -//! # base: Option, -//! # weight: f32 -//! # }; +//! use ffi_convert::{AsRust, CDrop, CReprOf, RawBorrow, RawPointerConverter}; //! use libc::{c_char, c_float}; //! -//! struct Sauce {}; +//! pub struct Sauce { +//! pub spiciness: f32, +//! } +//! +//! #[repr(C)] //! #[derive(CReprOf, AsRust, CDrop, RawPointerConverter)] //! #[target_type(Sauce)] -//! struct CSauce {}; +//! 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)] @@ -73,134 +62,185 @@ //! pub base: *const CSauce, //! pub weight: c_float, //! } -//! ``` //! -//! You may have noticed that you have to derive two traits : the CDrop trait and the RawPointerConverter trait. +//! // Rust -> C +//! let pizza = Pizza { +//! name: "Margarita".to_owned(), +//! base: Some(Sauce { spiciness: 1.5 }), +//! weight: 450.0, +//! }; +//! let c_pizza = CPizza::c_repr_of(pizza).unwrap(); +//! +//! // C -> Rust +//! let pizza_again: Pizza = c_pizza.as_rust().unwrap(); +//! assert_eq!(pizza_again.name, "Margarita"); +//! // `c_pizza` still owns the C strings it allocated; its `Drop` impl will +//! // free them when it goes out of scope. +//! ``` //! -//! 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. +//! Two things to notice: +//! +//! - `CSauce` derives [`RawPointerConverter`] because `CPizza::base` stores a +//! `*const CSauce`, and the derived [`CReprOf`] for `CPizza` needs to +//! convert a `CSauce` value into that raw pointer. +//! - `CPizza::base` is annotated with `#[nullable]` because the Rust field is +//! `Option`; without the attribute the derive would not know to map +//! `None` to the null pointer. +//! +//! # Type mapping +//! +//! `T: CReprOf + AsRust` — in the table below, `T` is the C-compatible +//! 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` | +//! +//! Users are free to define additional C-compatible layouts for their own +//! container-like types by implementing the traits directly. +//! +//! # 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 view. | +//! | [`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. | +//! +//! All four conversion traits can be derived. The derives are re-exported from +//! this crate; see [`ffi-convert-derive`](https://docs.rs/ffi-convert-derive) +//! for the full list of supported attributes. +//! +//! # Deriving the traits +//! +//! The derives cover the common cases. They expect: +//! +//! - [`CReprOf`], [`AsRust`], and [`CDrop`] are derived **as a set** on the +//! same type, or all written by hand. The three impls share an ownership +//! contract (each pointer field is a `Box::into_raw`'d value, owned by +//! the C-compatible struct), and the derives rely on that contract. Mixing +//! a derived [`CDrop`] with a hand-written [`CReprOf`] that allocates +//! differently — or the other way around — will cause double frees, leaks, +//! or UB. If one of them needs custom behavior for a specific field, +//! reach for `#[c_repr_of_convert]` / `#[as_rust_extra_field]` instead of +//! writing one impl by hand. +//! - `#[target_type(Path)]` on every struct or enum that derives [`CReprOf`] +//! or [`AsRust`], pointing at the idiomatic Rust type being mirrored. +//! [`CDrop`] and [`RawPointerConverter`] do not need it. +//! - `#[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. +//! - Any nested C-compatible struct used behind a pointer implements +//! [`RawPointerConverter`] (typically via `#[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. | +//! | `#[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. Excludes the field from `AsRust`. | +//! | `#[as_rust_extra_field(name = expr)]` | struct | `AsRust` | Initialise an extra field on the Rust side that has no C counterpart. Repeatable. | +//! | `#[no_drop_impl]` | struct / enum | `CDrop` | Only implement [`CDrop`]; skip the blanket [`Drop`] impl so you can write one manually. | +//! +//! Only unit enums (variants without fields) are supported by the derives for +//! enums. +//! +//! # Caveats with derivation of `CReprOf`, `AsRust`, and `CDrop` +//! +//! The derives are intentionally conservative. The most common pitfalls are: +//! +//! ## Derive all three, or hand-write all three +//! +//! [`CReprOf`], [`AsRust`], and [`CDrop`] share an ownership contract: the +//! derived [`CDrop`] assumes each pointer field was allocated by the derived +//! [`CReprOf`] with `Box::into_raw`, and the derived [`AsRust`] reads +//! pointer fields under the same assumption. Mixing a derived impl with a +//! hand-written one is how you end up with double frees, leaks, or UB. If a +//! single field needs custom handling, reach for `#[c_repr_of_convert]` or +//! `#[as_rust_extra_field]` instead of writing a full impl manually. +//! +//! ## Ownership of pointer fields +//! +//! A derived [`CDrop`] implementation assumes every pointer field points to a +//! [`Box`] allocated by this crate — typically because the struct was created +//! by [`CReprOf::c_repr_of`]. If you build the C struct manually with pointers +//! you do not own (for example pointers obtained from C, or stack addresses), +//! letting it drop will trigger undefined behavior. +//! +//! Similarly, **do not share inner pointers** between two owning C structs. +//! Both will try to free them. +//! +//! ## Pointers coming from C +//! +//! When a C-allocated struct is handed to Rust, convert it with +//! [`AsRust::as_rust`] as soon as possible to obtain an owned Rust value, and +//! leave the cleanup of the C-side memory to the C caller. Do **not** turn a +//! borrow into a `Box` (for example via [`RawPointerConverter::from_raw_pointer`]) +//! unless Rust actually owns the allocation. +//! +//! ## `#[nullable]` and `Option` must agree +//! +//! If the Rust side is `Option`, the C-side pointer field must carry +//! `#[nullable]` — otherwise the derives won't compile, because the generated +//! code tries to feed an `Option` into `T::c_repr_of` (or write a `Some(…)` +//! into a non-optional Rust field). The single `#[nullable]` attribute is +//! shared by all three derives. +//! +//! ## Detecting C strings +//! +//! The derives treat a field as a C string only when its type spelling ends in +//! `c_char` (for example `*const libc::c_char` or `*const c_char`). Aliases +//! introduced via `type` declarations are not recognised — use the `c_char` +//! spelling directly, or implement [`CReprOf`] / [`AsRust`] manually. +//! +//! ## Multi-level pointer fields +//! +//! Fields with more than one level of pointer indirection (such as +//! `*const *const CFoo`) are not supported by the derives and must be +//! implemented manually. +//! +//! ## Only unit enums can be derived +//! +//! `#[derive(CReprOf, AsRust, CDrop)]` on an enum requires every variant to be +//! a unit variant. Enums carrying data need a manual implementation. //! -//! 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`). +//! ## `target_type` is required //! -//! You can now pass the `CPizza` struct through your FFI boundary ! +//! Both `CReprOf` and `AsRust` require a `#[target_type(...)]` attribute on +//! the struct or enum. Without it the derive panics at compile time. +//! +//! ## `#[c_repr_of_convert]` disables `AsRust` for that field +//! +//! A field annotated with `#[c_repr_of_convert]` is skipped by the `AsRust` +//! derive. If the Rust struct still has a matching field, provide it via +//! `#[as_rust_extra_field(name = expr)]` on the struct; otherwise the derive +//! will fail to compile. +//! +//! # Interop checklist //! - -//! ## 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
-//! - -//! ## 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; -//! } -//! ``` - -//! 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; -//! } -//! ``` - -//! 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. +//! When exposing a function through FFI, the typical flow is: //! -//! This conversion trait comes in handy for C-like struct that have fields that points to other structs. +//! 1. Receive a `*const CInput` from C, convert it to an owned `Input` with +//! [`AsRust`] (or use [`RawBorrow`] to inspect without taking 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 the C caller invokes when it is +//! done; that function should take back ownership with +//! [`RawPointerConverter::from_raw_pointer`] and let the value drop. pub use ffi_convert_derive::*; From 0ef4a8c7154b24b4835d5457fc73692f59c9ec08 Mon Sep 17 00:00:00 2001 From: Thibaut Lorrain Date: Tue, 21 Apr 2026 00:56:23 +0200 Subject: [PATCH 2/5] improve doc, part 2 --- ffi-convert-derive/src/lib.rs | 70 ++--- ffi-convert-extra-ctypes/src/c_array.rs | 7 +- .../src/c_string_array.rs | 9 +- ffi-convert/src/conversions.rs | 139 ++++---- ffi-convert/src/lib.rs | 297 +++++++++--------- 5 files changed, 263 insertions(+), 259 deletions(-) diff --git a/ffi-convert-derive/src/lib.rs b/ffi-convert-derive/src/lib.rs index f6e1f36..6128943 100644 --- a/ffi-convert-derive/src/lib.rs +++ b/ffi-convert-derive/src/lib.rs @@ -26,20 +26,15 @@ 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`. String fields (`*const c_char`) are -/// re-allocated as C strings, pointer fields are boxed via +/// `#[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 scalar fields are passed through unchanged. -/// -/// `CReprOf` and [`CDrop`](../ffi_convert/trait.CDrop.html) share an -/// ownership contract: each pointer field produced by `c_repr_of` is a -/// `Box::into_raw`'d value that the derived `CDrop` reclaims with -/// `Box::from_raw`. The recommended shape is -/// `#[derive(CReprOf, AsRust, CDrop)]` as a set — deriving `CReprOf` while -/// hand-writing `CDrop` (or the reverse) breaks the contract and causes -/// double frees or leaks. For per-field customization, prefer -/// `#[c_repr_of_convert]` / `#[as_rust_extra_field]` over hand-writing one -/// of the impls. +/// and remaining fields go through their own `CReprOf` impl. +/// +/// The recommended shape is `#[derive(CReprOf, AsRust, CDrop)]` as a set: the +/// three derives share layout assumptions about pointer fields. For per-field +/// customization, prefer `#[c_repr_of_convert]` / `#[as_rust_extra_field]` +/// over hand-writing a whole impl. /// /// # Struct-level attributes /// @@ -73,16 +68,14 @@ pub fn creprof_derive(token_stream: TokenStream) -> TokenStream { /// 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 strings are decoded as UTF-8 -/// and copied; pointer fields are borrowed via -/// [`RawBorrow`](../ffi_convert/trait.RawBorrow.html) and then recursively -/// converted with `AsRust`. +/// 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 ownership -/// contract that `CReprOf` / `CDrop` use (each pointer is a -/// `Box::into_raw`'d value). If you derive any of the three, derive all of -/// them; mixing a derived `AsRust` with a hand-written `CReprOf` that -/// allocates pointer fields differently is undefined behavior. +/// 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 /// @@ -123,36 +116,31 @@ pub fn asrust_derive(token_stream: TokenStream) -> TokenStream { /// Derive [`CDrop`](../ffi_convert/trait.CDrop.html) and (by default) [`Drop`] /// for a struct or unit enum. /// -/// The generated `do_drop` takes back ownership of every owning pointer field -/// with [`RawPointerConverter::from_raw_pointer`](../ffi_convert/trait.RawPointerConverter.html) -/// and lets it drop. Scalar and array fields are left to Rust's regular drop -/// glue. +/// 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 — typically -/// because it was built via `CReprOf::c_repr_of`. Dropping a value built from -/// borrowed or shared pointers is undefined behavior. -/// -/// `CDrop` and [`CReprOf`](../ffi_convert/trait.CReprOf.html) must agree on -/// how pointer fields are allocated: derive both together, or write both by -/// hand. Mixing a derived `CDrop` with a hand-written `CReprOf` (or the -/// reverse) breaks the contract and leads to double frees or leaks. +/// because it was built via `CReprOf::c_repr_of`. Derive `CReprOf`, `AsRust`, +/// and `CDrop` together to keep their layout assumptions in sync. /// -/// The default output is the intended shape: both impls should coexist, so -/// that letting a value go out of scope actually frees the memory. A -/// `CDrop` impl without a matching `Drop` silently leaks every pointer -/// field. +/// 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 +/// (no one calls `do_drop`). /// /// # 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` -/// must call `do_drop`, otherwise the struct leaks. +/// 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 agree. +/// - `#[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(); diff --git a/ffi-convert-extra-ctypes/src/c_array.rs b/ffi-convert-extra-ctypes/src/c_array.rs index f914873..30adff8 100644 --- a/ffi-convert-extra-ctypes/src/c_array.rs +++ b/ffi-convert-extra-ctypes/src/c_array.rs @@ -13,9 +13,10 @@ use ffi_convert::{ /// 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 `memcpy`-style path, -/// reusing the same buffer for both sides. Otherwise each element is -/// converted individually through its `CReprOf` / `AsRust` implementation. +/// `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 diff --git a/ffi-convert-extra-ctypes/src/c_string_array.rs b/ffi-convert-extra-ctypes/src/c_string_array.rs index 88f33de..1a387ec 100644 --- a/ffi-convert-extra-ctypes/src/c_string_array.rs +++ b/ffi-convert-extra-ctypes/src/c_string_array.rs @@ -4,13 +4,12 @@ use ffi_convert::{ AsRust, AsRustError, CDrop, CDropError, CReprOf, CReprOfError, RawBorrow, RawPointerConverter, }; -/// A `#[repr(C)]` mirror of `Vec`: a contiguous array of -/// nul-terminated C strings. +/// A `#[repr(C)]` mirror of `Vec`. /// /// Layout is a `(data, size)` pair where `data` is a pointer to a -/// heap-allocated array of `*const c_char`. Each element is itself an owned -/// C string allocated by the Rust side. The whole structure is freed via -/// [`CDrop`](ffi_convert::CDrop) / [`Drop`]. +/// 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 diff --git a/ffi-convert/src/conversions.rs b/ffi-convert/src/conversions.rs index 8168956..b2594d4 100644 --- a/ffi-convert/src/conversions.rs +++ b/ffi-convert/src/conversions.rs @@ -90,15 +90,11 @@ pub enum CReprOfError { /// `#[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 -/// implementation owns any heap memory it allocates, and that memory is -/// reclaimed by the corresponding [`CDrop`] implementation. -/// -/// `CReprOf` and [`CDrop`] share an ownership contract — each allocation -/// `c_repr_of` performs must be freeable by `do_drop`, and vice versa. The -/// derives enforce that contract by generating both sides together. If one -/// is derived, the other must be too; mixing a derived impl with a -/// hand-written one is a recipe for double frees or leaks. +/// 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. The recommended way to provide both +/// is `#[derive(CReprOf, AsRust, CDrop)]` — 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; @@ -115,27 +111,24 @@ pub enum CDropError { Other(#[from] Box), } -/// Releases any heap memory owned by a C-compatible value that is not managed -/// by Rust's regular `Drop` mechanism (typically `Box`-allocated data behind -/// raw pointer fields). +/// 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`]). /// -/// The [`#[derive(CDrop)]`](ffi_convert_derive::CDrop) macro emits both a -/// [`CDrop`] and a matching [`Drop`] impl that calls -/// [`do_drop`](CDrop::do_drop). The two should always ship together — -/// a [`CDrop`] impl by itself does nothing until something calls `do_drop`, -/// so leaving the value to Rust's regular `drop` leaks every pointer field -/// it owns. Use `#[no_drop_impl]` only when you need to write [`Drop`] -/// yourself, and make sure that manual [`Drop`] calls `do_drop`. +/// 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 hand-written [`Drop`] must call `do_drop` +/// itself, otherwise the pointer fields are leaked. /// -/// [`CDrop`] and [`CReprOf`] share an ownership contract: the derived -/// [`CDrop`] assumes every pointer field was produced by the derived -/// [`CReprOf`] (i.e. via `Box::into_raw`). Mixing a derived [`CDrop`] with -/// a hand-written [`CReprOf`] — or vice versa — is how you get double -/// frees or leaks. Derive both or write both. +/// The recommended way to provide `CDrop`, [`CReprOf`], and [`AsRust`] is +/// to derive them together — see +/// [Deriving the traits](crate#deriving-the-traits). pub trait CDrop { - /// Release any Rust-owned memory referenced by `self`. Typically called - /// from the generated [`Drop`] implementation; in that case errors are - /// silently ignored. + /// 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>; } @@ -156,64 +149,75 @@ pub enum AsRustError { /// Non-consuming conversion **from** a `#[repr(C)]` value **back** to an /// owned, idiomatic Rust value. /// -/// `AsRust` takes `&self` and returns a freshly-allocated `U`, performing a -/// deep copy of any pointer field it owns. After the call the original -/// C-compatible struct is still valid — only the C side is expected to free -/// it. +/// `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 (typically via [`CDrop`] on +/// the C side). +/// +/// 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 whenever a raw pointer was expected to be non-null but was. +/// 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; -/// Boxes a Rust value into a raw pointer suitable for crossing an FFI -/// boundary, and takes it back. +/// 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. /// -/// `into_raw_pointer` leaks the value (via [`Box::into_raw`]) and must be -/// paired with a later [`from_raw_pointer`](RawPointerConverter::from_raw_pointer) -/// or [`drop_raw_pointer`](RawPointerConverter::drop_raw_pointer) call to -/// avoid a leak. If you only need to read the value behind the pointer -/// without taking ownership — because the C caller still owns the allocation -/// — use [`RawBorrow`] instead. +/// 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` family is unsafe because the compiler cannot verify -/// that the pointer was actually produced by `into_raw_pointer`. Calling it -/// twice on the same pointer is a double free. +/// 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(|_| ()) } @@ -250,16 +254,18 @@ pub unsafe fn take_back_from_raw_pointer_mut( /// 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. Blanket-implemented for every `T`; also -/// implemented for [`std::ffi::CStr`] over `*const libc::c_char`. +/// 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 { /// Borrow the value behind `input`, or return /// [`UnexpectedNullPointerError`] if it is null. /// /// # Safety - /// This is a thin wrapper around `<*const T>::as_ref` and is unsafe for - /// the same reasons: `input` must point to a valid, properly aligned - /// `T` that lives for at least `'a`. + /// 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>; } @@ -269,8 +275,7 @@ pub trait RawBorrowMut { /// [`UnexpectedNullPointerError`] if it is null. /// /// # Safety - /// This is a thin wrapper around `<*mut T>::as_mut` and is unsafe for 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>; } diff --git a/ffi-convert/src/lib.rs b/ffi-convert/src/lib.rs index 43ff681..d2d3b50 100644 --- a/ffi-convert/src/lib.rs +++ b/ffi-convert/src/lib.rs @@ -1,36 +1,53 @@ //! 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`]. Together they form a small framework -//! that makes it hard to get ownership wrong while moving data across the -//! boundary in either direction. Deriving them (via the companion -//! [`ffi-convert-derive`](https://docs.rs/ffi-convert-derive) crate, re-exported -//! here) removes most of the boilerplate. +//! 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 -//! so that users who want to define their own C layouts can skip them. +//! [`ffi-convert-extra-ctypes`](https://docs.rs/ffi-convert-extra-ctypes) +//! crate and can be pulled in on demand. //! //! # Philosophy //! -//! When a pointer crosses the FFI boundary, decide *once* which side owns it: -//! -//! - **Incoming from C**: immediately convert to an owned, idiomatic Rust value -//! with [`AsRust`]. After that call the Rust value is fully self-contained -//! and the C-side struct can be dropped or handed back to the caller. -//! - **Outgoing to C**: build a C-compatible struct from an owned Rust value -//! with [`CReprOf`]. That struct now owns whatever heap memory it points to -//! and must eventually be freed via [`CDrop`] (typically by sending the -//! pointer back to Rust through a `free`-style FFI function that calls -//! [`RawPointerConverter::from_raw_pointer`]). +//! `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() +//! ``` //! //! # Quick example //! -//! Define the Rust type you want to expose, then define its `#[repr(C)]` -//! mirror and derive the conversion traits. The mirror's fields use -//! C-compatible types (see [the mapping table](#type-mapping)). +//! 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)). //! //! ``` //! use ffi_convert::{AsRust, CDrop, CReprOf, RawBorrow, RawPointerConverter}; @@ -54,7 +71,7 @@ //! } //! //! #[repr(C)] -//! #[derive(CReprOf, AsRust, CDrop)] +//! #[derive(CReprOf, AsRust, CDrop, RawPointerConverter)] //! #[target_type(Pizza)] //! pub struct CPizza { //! pub name: *const c_char, @@ -62,35 +79,83 @@ //! pub base: *const CSauce, //! pub weight: c_float, //! } -//! -//! // Rust -> C -//! let pizza = Pizza { -//! name: "Margarita".to_owned(), -//! base: Some(Sauce { spiciness: 1.5 }), -//! weight: 450.0, -//! }; -//! let c_pizza = CPizza::c_repr_of(pizza).unwrap(); -//! -//! // C -> Rust -//! let pizza_again: Pizza = c_pizza.as_rust().unwrap(); -//! assert_eq!(pizza_again.name, "Margarita"); -//! // `c_pizza` still owns the C strings it allocated; its `Drop` impl will -//! // free them when it goes out of scope. //! ``` //! //! Two things to notice: //! //! - `CSauce` derives [`RawPointerConverter`] because `CPizza::base` stores a -//! `*const CSauce`, and the derived [`CReprOf`] for `CPizza` needs to -//! convert a `CSauce` value into that raw pointer. -//! - `CPizza::base` is annotated with `#[nullable]` because the Rust field is -//! `Option`; without the attribute the derive would not know to map -//! `None` to the null pointer. +//! `*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. +//! +//! With the derives in place, an FFI wrapper is just three small functions — +//! one to read a C-owned value, one to hand a Rust value to C, and one to free +//! it: +//! +//! ``` +//! # 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); +//! } +//! +//! // 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) }; +//! } +//! ``` //! //! # Type mapping //! //! `T: CReprOf + AsRust` — in the table below, `T` is the C-compatible -//! type and `U` is the idiomatic Rust type. +//! Rust type and `U` is the idiomatic Rust type. //! //! | C type | Rust type (`U`) | C-compatible Rust type (`T`) | Provided by | //! |------------------------|-------------------|-------------------------------------------------------------------------------------------------------------|------------------------------| @@ -104,45 +169,45 @@ //! | `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` | //! -//! Users are free to define additional C-compatible layouts for their own -//! container-like types by implementing the traits directly. +//! The derives accept both `*const T` and `*mut T` for any pointer row. Other +//! container layouts can be supported by writing the trait impls by hand. //! //! # 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 view. | +//! | [`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. | //! -//! All four conversion traits can be derived. The derives are re-exported from -//! this crate; see [`ffi-convert-derive`](https://docs.rs/ffi-convert-derive) +//! [`CReprOf`], [`AsRust`], [`CDrop`], and [`RawPointerConverter`] all have +//! derive macros; see [`ffi-convert-derive`](https://docs.rs/ffi-convert-derive) //! for the full list of supported attributes. //! //! # Deriving the traits //! -//! The derives cover the common cases. They expect: -//! -//! - [`CReprOf`], [`AsRust`], and [`CDrop`] are derived **as a set** on the -//! same type, or all written by hand. The three impls share an ownership -//! contract (each pointer field is a `Box::into_raw`'d value, owned by -//! the C-compatible struct), and the derives rely on that contract. Mixing -//! a derived [`CDrop`] with a hand-written [`CReprOf`] that allocates -//! differently — or the other way around — will cause double frees, leaks, -//! or UB. If one of them needs custom behavior for a specific field, -//! reach for `#[c_repr_of_convert]` / `#[as_rust_extra_field]` instead of -//! writing one impl by hand. +//! The derives are the intended way to use the crate — `#[derive(CReprOf, +//! AsRust, CDrop)]` on a `#[repr(C)]` mirror handles the vast majority of +//! structs and enums. Deriving all three together is recommended: they share +//! layout assumptions about pointer fields and are designed to be used as a +//! set. +//! +//! The derives expect: +//! //! - `#[target_type(Path)]` on every struct or enum that derives [`CReprOf`] //! or [`AsRust`], pointing at the idiomatic Rust type being mirrored. //! [`CDrop`] and [`RawPointerConverter`] do not need it. //! - `#[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. -//! - Any nested C-compatible struct used behind a pointer implements -//! [`RawPointerConverter`] (typically via `#[derive(RawPointerConverter)]`). +//! 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: //! @@ -151,96 +216,42 @@ //! | `#[target_type(Path)]` | struct / enum | `CReprOf`, `AsRust` | The idiomatic Rust type this C-compatible type mirrors. | //! | `#[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. Excludes the field from `AsRust`. | -//! | `#[as_rust_extra_field(name = expr)]` | struct | `AsRust` | Initialise an extra field on the Rust side that has no C counterpart. Repeatable. | +//! | `#[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`. | +//! | `#[as_rust_extra_field(name = expr)]` | struct | `AsRust` | Initialise an extra field on the Rust side that has no C counterpart. Repeatable; `self` (the C-side value) is in scope inside `expr`. | //! | `#[no_drop_impl]` | struct / enum | `CDrop` | Only implement [`CDrop`]; skip the blanket [`Drop`] impl so you can write one manually. | //! -//! Only unit enums (variants without fields) are supported by the derives for -//! enums. -//! -//! # Caveats with derivation of `CReprOf`, `AsRust`, and `CDrop` -//! -//! The derives are intentionally conservative. The most common pitfalls are: -//! -//! ## Derive all three, or hand-write all three -//! -//! [`CReprOf`], [`AsRust`], and [`CDrop`] share an ownership contract: the -//! derived [`CDrop`] assumes each pointer field was allocated by the derived -//! [`CReprOf`] with `Box::into_raw`, and the derived [`AsRust`] reads -//! pointer fields under the same assumption. Mixing a derived impl with a -//! hand-written one is how you end up with double frees, leaks, or UB. If a -//! single field needs custom handling, reach for `#[c_repr_of_convert]` or -//! `#[as_rust_extra_field]` instead of writing a full impl manually. -//! -//! ## Ownership of pointer fields -//! -//! A derived [`CDrop`] implementation assumes every pointer field points to a -//! [`Box`] allocated by this crate — typically because the struct was created -//! by [`CReprOf::c_repr_of`]. If you build the C struct manually with pointers -//! you do not own (for example pointers obtained from C, or stack addresses), -//! letting it drop will trigger undefined behavior. -//! -//! Similarly, **do not share inner pointers** between two owning C structs. -//! Both will try to free them. -//! -//! ## Pointers coming from C -//! -//! When a C-allocated struct is handed to Rust, convert it with -//! [`AsRust::as_rust`] as soon as possible to obtain an owned Rust value, and -//! leave the cleanup of the C-side memory to the C caller. Do **not** turn a -//! borrow into a `Box` (for example via [`RawPointerConverter::from_raw_pointer`]) -//! unless Rust actually owns the allocation. -//! -//! ## `#[nullable]` and `Option` must agree -//! -//! If the Rust side is `Option`, the C-side pointer field must carry -//! `#[nullable]` — otherwise the derives won't compile, because the generated -//! code tries to feed an `Option` into `T::c_repr_of` (or write a `Some(…)` -//! into a non-optional Rust field). The single `#[nullable]` attribute is -//! shared by all three derives. -//! -//! ## Detecting C strings -//! -//! The derives treat a field as a C string only when its type spelling ends in -//! `c_char` (for example `*const libc::c_char` or `*const c_char`). Aliases -//! introduced via `type` declarations are not recognised — use the `c_char` -//! spelling directly, or implement [`CReprOf`] / [`AsRust`] manually. -//! -//! ## Multi-level pointer fields -//! -//! Fields with more than one level of pointer indirection (such as -//! `*const *const CFoo`) are not supported by the derives and must be -//! implemented manually. -//! -//! ## Only unit enums can be derived -//! -//! `#[derive(CReprOf, AsRust, CDrop)]` on an enum requires every variant to be -//! a unit variant. Enums carrying data need a manual implementation. -//! -//! ## `target_type` is required -//! -//! Both `CReprOf` and `AsRust` require a `#[target_type(...)]` attribute on -//! the struct or enum. Without it the derive panics at compile time. -//! -//! ## `#[c_repr_of_convert]` disables `AsRust` for that field -//! -//! A field annotated with `#[c_repr_of_convert]` is skipped by the `AsRust` -//! derive. If the Rust struct still has a matching field, provide it via -//! `#[as_rust_extra_field(name = expr)]` on the struct; otherwise the derive -//! will fail to compile. +//! ## Constraints +//! +//! A handful of layouts fall outside what the derives support and need a +//! manual implementation: +//! +//! - **C strings**: a field is recognised 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 recognised; either use the `c_char` spelling +//! directly or implement the traits by hand. +//! - **Multi-level pointer fields** (such as `*const *const CFoo`) are +//! accepted by the [`AsRust`] derive only when the field is also +//! `#[nullable]`. Non-trivial multi-level layouts are easier to write by +//! hand. +//! - **Enums with data**: the derives accept enums only when every variant +//! is a unit variant; variants carrying data need a manual impl. +//! - **`#[c_repr_of_convert]` skips the field on the `AsRust` side**. If the +//! Rust struct still has a matching field, reconstruct it with +//! `#[as_rust_extra_field(name = expr)]` on the struct; otherwise the +//! generated `AsRust` impl will not compile. //! //! # Interop checklist //! -//! When exposing a function through FFI, the typical flow is: +//! A typical FFI-exposed function follows this pattern: //! -//! 1. Receive a `*const CInput` from C, convert it to an owned `Input` with -//! [`AsRust`] (or use [`RawBorrow`] to inspect without taking ownership). +//! 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 the C caller invokes when it is -//! done; that function should take back ownership with -//! [`RawPointerConverter::from_raw_pointer`] and let the value drop. +//! 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::*; From 412a68e1a314fa925fa15479fc59b02a62b09938 Mon Sep 17 00:00:00 2001 From: Thibaut Lorrain Date: Sun, 3 May 2026 22:58:33 +0200 Subject: [PATCH 3/5] correct documentation --- ffi-convert-derive/src/lib.rs | 14 +-- ffi-convert/src/conversions.rs | 182 ++++++++++++++++----------------- ffi-convert/src/lib.rs | 93 ++++++++--------- 3 files changed, 136 insertions(+), 153 deletions(-) diff --git a/ffi-convert-derive/src/lib.rs b/ffi-convert-derive/src/lib.rs index 6128943..6cbeb32 100644 --- a/ffi-convert-derive/src/lib.rs +++ b/ffi-convert-derive/src/lib.rs @@ -31,11 +31,6 @@ use rawpointerconverter::impl_rawpointerconverter_macro; /// [`RawPointerConverter::into_raw_pointer`](../ffi_convert/trait.RawPointerConverter.html), /// and remaining fields go through their own `CReprOf` impl. /// -/// The recommended shape is `#[derive(CReprOf, AsRust, CDrop)]` as a set: the -/// three derives share layout assumptions about pointer fields. For per-field -/// customization, prefer `#[c_repr_of_convert]` / `#[as_rust_extra_field]` -/// over hand-writing a whole impl. -/// /// # Struct-level attributes /// /// - `#[target_type(Path)]` — **required**. The idiomatic Rust type this @@ -121,14 +116,13 @@ pub fn asrust_derive(token_stream: TokenStream) -> TokenStream { /// 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 — typically -/// because it was built via `CReprOf::c_repr_of`. Derive `CReprOf`, `AsRust`, -/// and `CDrop` together to keep their layout assumptions in sync. +/// 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 -/// (no one calls `do_drop`). +/// impl without a matching `Drop` leaks every pointer field on scope exit. /// /// # Struct-level attributes /// diff --git a/ffi-convert/src/conversions.rs b/ffi-convert/src/conversions.rs index b2594d4..caccbea 100644 --- a/ffi-convert/src/conversions.rs +++ b/ffi-convert/src/conversions.rs @@ -4,76 +4,6 @@ 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 { @@ -92,9 +22,9 @@ pub enum CReprOfError { /// 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. The recommended way to provide both -/// is `#[derive(CReprOf, AsRust, CDrop)]` — see -/// [Deriving the traits](crate#deriving-the-traits). +/// 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; @@ -115,16 +45,14 @@ pub enum CDropError { /// 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 +/// 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 hand-written [`Drop`] must call `do_drop` +/// [`Drop`] impl; in that case a handwritten [`Drop`] must call `do_drop` /// itself, otherwise the pointer fields are leaked. /// -/// The recommended way to provide `CDrop`, [`CReprOf`], and [`AsRust`] is -/// to derive them together — see -/// [Deriving the traits](crate#deriving-the-traits). +/// 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 @@ -152,8 +80,7 @@ pub enum AsRustError { /// `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 (typically via [`CDrop`] on -/// the C side). +/// 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). @@ -358,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); @@ -393,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); @@ -414,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 d2d3b50..560d4fb 100644 --- a/ffi-convert/src/lib.rs +++ b/ffi-convert/src/lib.rs @@ -13,7 +13,7 @@ //! //! # Philosophy //! -//! `ffi-convert`'s memory-management model makes as few assumptions as +//! `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: @@ -91,7 +91,7 @@ //! `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. //! -//! With the derives in place, an FFI wrapper is just three small functions — +//! 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: //! @@ -157,48 +157,50 @@ //! `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` | +//! | 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` | +//! | `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. Other -//! container layouts can be supported by writing the trait impls by hand. +//! 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. | +//! | 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; see [`ffi-convert-derive`](https://docs.rs/ffi-convert-derive) -//! for the full list of supported attributes. +//! derive macros. //! //! # Deriving the traits //! -//! The derives are the intended way to use the crate — `#[derive(CReprOf, -//! AsRust, CDrop)]` on a `#[repr(C)]` mirror handles the vast majority of -//! structs and enums. Deriving all three together is recommended: they share -//! layout assumptions about pointer fields and are designed to be used as a -//! set. +//! 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. -//! [`CDrop`] and [`RawPointerConverter`] do not need it. //! - `#[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 @@ -211,35 +213,26 @@ //! //! 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. | -//! | `#[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. | +//! | 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`. | -//! | `#[as_rust_extra_field(name = expr)]` | struct | `AsRust` | Initialise an extra field on the Rust side that has no C counterpart. Repeatable; `self` (the C-side value) is in scope inside `expr`. | -//! | `#[no_drop_impl]` | struct / enum | `CDrop` | Only implement [`CDrop`]; skip the blanket [`Drop`] impl so you can write one manually. | //! //! ## Constraints //! -//! A handful of layouts fall outside what the derives support and need a -//! manual implementation: -//! -//! - **C strings**: a field is recognised as a C string only when the +//! - **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 recognised; either use the `c_char` spelling -//! directly or implement the traits by hand. +//! 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]`. Non-trivial multi-level layouts are easier to write by -//! hand. -//! - **Enums with data**: the derives accept enums only when every variant -//! is a unit variant; variants carrying data need a manual impl. -//! - **`#[c_repr_of_convert]` skips the field on the `AsRust` side**. If the -//! Rust struct still has a matching field, reconstruct it with -//! `#[as_rust_extra_field(name = expr)]` on the struct; otherwise the -//! generated `AsRust` impl will not compile. +//! `#[nullable]`. +//! - **Enums with data**:not supported. the derives accept enums only when +//! every variant is a unit variant. //! //! # Interop checklist //! From db81f76989c8dd1e4e4e43eaa69e2904a46a55a4 Mon Sep 17 00:00:00 2001 From: Thibaut Lorrain Date: Sun, 3 May 2026 23:06:42 +0200 Subject: [PATCH 4/5] correct doc of extra types --- ffi-convert-extra-ctypes/src/c_array.rs | 3 ++- ffi-convert-extra-ctypes/src/c_range.rs | 4 ++-- ffi-convert-extra-ctypes/src/c_string_array.rs | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/ffi-convert-extra-ctypes/src/c_array.rs b/ffi-convert-extra-ctypes/src/c_array.rs index 30adff8..aa451e5 100644 --- a/ffi-convert-extra-ctypes/src/c_array.rs +++ b/ffi-convert-extra-ctypes/src/c_array.rs @@ -7,7 +7,8 @@ use ffi_convert::{ take_back_from_raw_pointer, take_back_from_raw_pointer_mut, }; -/// A `#[repr(C)]` mirror of [`Vec`] where `T: CReprOf + AsRust`. +/// 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`. diff --git a/ffi-convert-extra-ctypes/src/c_range.rs b/ffi-convert-extra-ctypes/src/c_range.rs index 5897711..5133b04 100644 --- a/ffi-convert-extra-ctypes/src/c_range.rs +++ b/ffi-convert-extra-ctypes/src/c_range.rs @@ -2,8 +2,8 @@ use std::ops::Range; use ffi_convert::{AsRust, AsRustError, CDrop, CDropError, CReprOf, CReprOfError}; -/// A `#[repr(C)]` mirror of [`std::ops::Range`] where -/// `T: CReprOf + AsRust`. +/// 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. diff --git a/ffi-convert-extra-ctypes/src/c_string_array.rs b/ffi-convert-extra-ctypes/src/c_string_array.rs index 1a387ec..4d05ba6 100644 --- a/ffi-convert-extra-ctypes/src/c_string_array.rs +++ b/ffi-convert-extra-ctypes/src/c_string_array.rs @@ -4,7 +4,8 @@ use ffi_convert::{ AsRust, AsRustError, CDrop, CDropError, CReprOf, CReprOfError, RawBorrow, RawPointerConverter, }; -/// A `#[repr(C)]` mirror of `Vec`. +/// 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 From e1b2f587173f28e9b8a2dbc017f27270f0c8f218 Mon Sep 17 00:00:00 2001 From: Thibaut Lorrain Date: Fri, 8 May 2026 15:05:24 +0200 Subject: [PATCH 5/5] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) 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