ffi_convert/lib.rs
1//! Traits and helpers to convert between idiomatic Rust values and C-compatible
2//! representations when crossing an FFI boundary.
3//!
4//! The crate is built around two conversion traits, [`CReprOf`] and [`AsRust`],
5//! and two supporting traits, [`CDrop`] and [`RawPointerConverter`]. Derive
6//! macros for all four are provided by the companion
7//! [`ffi-convert-derive`](https://docs.rs/ffi-convert-derive) crate and
8//! re-exported here.
9//!
10//! Common containers (arrays, string arrays, ranges) live in the separate
11//! [`ffi-convert-extra-ctypes`](https://docs.rs/ffi-convert-extra-ctypes)
12//! crate and can be pulled in on demand.
13//!
14//! # Philosophy
15//!
16//! `ffi-convert`'s memory-management model makes as few assumptions as
17//! possible about how the C side allocates, holds, or frees memory.
18//!
19//! Two traits cover the two directions across the FFI boundary:
20//!
21//! - **Incoming from C** — [`AsRust`] takes a `&CFoo` and returns an owned
22//! `Foo` built by deep-copying every field. It is a defensive copy: once
23//! `as_rust` returns, the resulting Rust value does not reference any
24//! C-owned memory, and nothing else in the crate reads from the original
25//! pointer afterwards. The C caller is free to keep, reuse, or release
26//! the pointer however its own rules require.
27//! - **Outgoing to C** — [`CReprOf`] consumes a `Foo` and produces a `CFoo`
28//! that owns any heap memory its pointer fields reference. The `CFoo` is
29//! then handed to C as a raw pointer; to release everything, C sends the
30//! pointer back to Rust through a `free`-style FFI function that lets the
31//! value drop (releasing its pointer fields via [`CDrop`]).
32//!
33//! ```text
34//! CPizza::c_repr_of(pizza)
35//! ┌───────────────────────────────┐
36//! │ ▼
37//! ┌──────────┐ ┌──────────┐
38//! │ Pizza │ │ CPizza │
39//! │ (Rust) │ │ (C) │
40//! └──────────┘ └──────────┘
41//! ▲ │
42//! └───────────────────────────────┘
43//! c_pizza.as_rust()
44//! ```
45//!
46//! # Quick example
47//!
48//! Define the Rust type you want to expose, then define a `#[repr(C)]` mirror
49//! and derive the conversion traits. The mirror's fields use C-compatible
50//! types (see [the mapping table](#type-mapping)).
51//!
52//! ```
53//! use ffi_convert::{AsRust, CDrop, CReprOf, RawBorrow, RawPointerConverter};
54//! use std::ffi::{c_char, c_float};
55//!
56//! pub struct Sauce {
57//! pub spiciness: f32,
58//! }
59//!
60//! #[repr(C)]
61//! #[derive(CReprOf, AsRust, CDrop, RawPointerConverter)]
62//! #[target_type(Sauce)]
63//! pub struct CSauce {
64//! pub spiciness: c_float,
65//! }
66//!
67//! pub struct Pizza {
68//! pub name: String,
69//! pub base: Option<Sauce>,
70//! pub weight: f32,
71//! }
72//!
73//! #[repr(C)]
74//! #[derive(CReprOf, AsRust, CDrop, RawPointerConverter)]
75//! #[target_type(Pizza)]
76//! pub struct CPizza {
77//! pub name: *const c_char,
78//! #[nullable]
79//! pub base: *const CSauce,
80//! pub weight: c_float,
81//! }
82//! ```
83//!
84//! Two things to notice:
85//!
86//! - `CSauce` derives [`RawPointerConverter`] because `CPizza::base` stores a
87//! `*const CSauce`; `CPizza` derives it too so it can itself be handed to C
88//! as a `*const CPizza`. In both cases the derived [`CReprOf`] turns a value
89//! into a raw pointer via `into_raw_pointer`.
90//! - `CPizza::base` carries `#[nullable]` because the Rust field is
91//! `Option<Sauce>`. The attribute tells the derives to map `None` to a null
92//! pointer on the way out and a null pointer to `None` on the way back.
93//!
94//! With the derives in place, let's write an FFI wrapper with three small functions —
95//! one to read a C-owned value, one to hand a Rust value to C, and one to free
96//! it:
97//!
98//! ```
99//! # use ffi_convert::{AsRust, CDrop, CReprOf, RawBorrow, RawPointerConverter};
100//! # use std::ffi::{c_char, c_float};
101//! # pub struct Sauce { pub spiciness: f32 }
102//! # #[repr(C)]
103//! # #[derive(CReprOf, AsRust, CDrop, RawPointerConverter)]
104//! # #[target_type(Sauce)]
105//! # pub struct CSauce { pub spiciness: c_float }
106//! # pub struct Pizza {
107//! # pub name: String,
108//! # pub base: Option<Sauce>,
109//! # pub weight: f32,
110//! # }
111//! # #[repr(C)]
112//! # #[derive(CReprOf, AsRust, CDrop, RawPointerConverter)]
113//! # #[target_type(Pizza)]
114//! # pub struct CPizza {
115//! # pub name: *const c_char,
116//! # #[nullable]
117//! # pub base: *const CSauce,
118//! # pub weight: c_float,
119//! # }
120//! // Read a CPizza handed to us by C: deep-copy its contents into an owned
121//! // Rust `Pizza`, then run whatever logic we need. The original pointer is
122//! // untouched; C keeps ownership of it.
123//! #[unsafe(no_mangle)]
124//! pub unsafe extern "C" fn inspect_pizza(c_pizza: *const CPizza) {
125//! let c_pizza = unsafe { CPizza::raw_borrow(c_pizza) }
126//! .expect("c_pizza must not be null");
127//! let pizza: Pizza = c_pizza.as_rust().expect("invalid CPizza contents");
128//! println!("{} ({}g)", pizza.name, pizza.weight);
129//! }
130//!
131//! // Build a Rust `Pizza`, convert it to `CPizza`, and hand C a raw pointer
132//! // via [`RawPointerConverter::into_raw_pointer`]. The caller must
133//! // eventually invoke `free_pizza` to release the allocation.
134//! #[unsafe(no_mangle)]
135//! pub extern "C" fn make_pizza() -> *const CPizza {
136//! let pizza = Pizza {
137//! name: "Margarita".to_owned(),
138//! base: Some(Sauce { spiciness: 1.5 }),
139//! weight: 450.0,
140//! };
141//! CPizza::c_repr_of(pizza)
142//! .expect("pizza name contains an interior NUL byte")
143//! .into_raw_pointer()
144//! }
145//!
146//! // Reclaim a pointer produced by `make_pizza`.
147//! // [`RawPointerConverter::drop_raw_pointer`] takes ownership back and
148//! // drops the value, releasing every inner pointer field via [`CDrop`].
149//! #[unsafe(no_mangle)]
150//! pub unsafe extern "C" fn free_pizza(c_pizza: *const CPizza) {
151//! let _ = unsafe { CPizza::drop_raw_pointer(c_pizza) };
152//! }
153//! ```
154//!
155//! # Type mapping
156//!
157//! `T: CReprOf<U> + AsRust<U>` — in the table below, `T` is the C-compatible
158//! Rust type and `U` is the idiomatic Rust type.
159//!
160//! | C type | Rust type (`U`) | C-compatible Rust type (`T`) | Provided by |
161//! |------------------------|-------------------|---------------------------------------------------------------------------------------------------------------------|------------------------------|
162//! | any scalar (`int`, …) | same scalar | same scalar | `ffi-convert` |
163//! | `const char*` | `String` | `*const std::ffi::c_char` | `ffi-convert` |
164//! | `const T*` | `U` | `*const T` | `ffi-convert` |
165//! | `T*` | `U` | `*mut T` | `ffi-convert` |
166//! | `const T*` (nullable) | `Option<U>` | `*const T` with `#[nullable]` | `ffi-convert` |
167//! | `T[N]` | `[U; N]` | `[T; N]` | `ffi-convert` |
168//! | `CArrayT` | `Vec<U>` | [`CArray<T>`](https://docs.rs/ffi-convert-extra-ctypes/latest/ffi_convert_extra_ctypes/struct.CArray.html) | `ffi-convert-extra-ctypes` |
169//! | `CStringArray` | `Vec<String>` | [`CStringArray`](https://docs.rs/ffi-convert-extra-ctypes/latest/ffi_convert_extra_ctypes/struct.CStringArray.html) | `ffi-convert-extra-ctypes` |
170//! | `CRangeT` | `Range<U>` | [`CRange<T>`](https://docs.rs/ffi-convert-extra-ctypes/latest/ffi_convert_extra_ctypes/struct.CRange.html) | `ffi-convert-extra-ctypes` |
171//!
172//! The derives accept both `*const T` and `*mut T` for any pointer row.
173//!
174//! # Traits at a glance
175//!
176//! | Trait | Direction | Purpose |
177//! |--------------------------|----------------------|-------------------------------------------------------------------------------------------------------|
178//! | [`CReprOf<U>`] | Rust → C | Consume an idiomatic Rust value and produce its C-compatible twin. |
179//! | [`AsRust<U>`] | C → Rust | Produce an owned Rust value from a borrowed C-compatible value. |
180//! | [`CDrop`] | cleanup | Free heap data owned by a C-compatible struct. |
181//! | [`RawPointerConverter`] | pointer boxing | Box a value into `*const T` / `*mut T` and take it back. |
182//! | [`RawBorrow`] | pointer borrowing | Borrow `&T` from a raw pointer without taking ownership. Returns an error if the pointer is null. |
183//! | [`RawBorrowMut`] | pointer borrowing | Borrow `&mut T` from a raw pointer without taking ownership. Returns an error if the pointer is null. |
184//!
185//! [`CReprOf`], [`AsRust`], [`CDrop`], and [`RawPointerConverter`] all have
186//! derive macros.
187//!
188//! # Deriving the traits
189//!
190//! The derives are the intended way to use the crate. Typical derive
191//! combinations on a `#[repr(C)]` type are:
192//!
193//! - `#[derive(CReprOf, CDrop)]` for a type created in Rust and read from C
194//! - `#[derive(AsRust)]` for a type created in C and read in Rust
195//! - `#[derive(AsRust, CReprOf, CDrop)]` for a type created and read in C and Rust
196//!
197//! Deriving `CDrop` and `CReprOf` together is recommended: `CDrop` assumes raw
198//! pointers were initialized the way the `CReprOf` derive initializes them.
199//!
200//! The derives expect:
201//!
202//! - `#[target_type(Path)]` on every struct or enum that derives [`CReprOf`]
203//! or [`AsRust`], pointing at the idiomatic Rust type being mirrored.
204//! - `#[nullable]` on every pointer field whose Rust counterpart is an
205//! [`Option`]. The attribute is shared by all three derives: [`CReprOf`]
206//! reads it to emit a null for `None`, [`AsRust`] to return `None` on a
207//! null pointer, and [`CDrop`] to skip the free on null. A mismatch
208//! between the Rust-side `Option<T>` and the C-side `#[nullable]` is a
209//! compile error.
210//! - [`RawPointerConverter`] to be implemented on any nested C-compatible
211//! struct reached through a pointer field, usually by
212//! `#[derive(RawPointerConverter)]`.
213//!
214//! The available attributes are:
215//!
216//! | Attribute | Applies to | Used by | Purpose |
217//! |------------------------------------------|-------------------------|-----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
218//! | `#[target_type(Path)]` | struct / enum | `CReprOf`, `AsRust` | The idiomatic Rust type this C-compatible type mirrors. |
219//! | `#[no_drop_impl]` | struct / enum | `CDrop` | Only implement [`CDrop`]; skip the blanket [`Drop`] impl so you can write one manually. |
220//! | `#[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`. |
221//! | `#[nullable]` | pointer field | `CReprOf`, `AsRust`, `CDrop`| Treat a `*const T` / `*mut T` as `Option<…>`. Required for every optional pointer field. |
222//! | `#[target_name(ident)]` | field | `CReprOf`, `AsRust` | Name of the corresponding field on the Rust side when it differs from the C-side name. |
223//! | `#[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`. |
224//!
225//! ## Constraints
226//!
227//! - **C strings**: a field is recognized as a C string only when the
228//! pointee's type name is literally `c_char` — `*const std::ffi::c_char`,
229//! `*mut std::ffi::c_char`, and `*const c_char` all qualify. A `type` alias
230//! for `c_char` is not recognized.
231//! - **Multi-level pointer fields** (such as `*const *const CFoo`) are
232//! accepted by the [`AsRust`] derive only when the field is also
233//! `#[nullable]`.
234//! - **Enums with data**:not supported. the derives accept enums only when
235//! every variant is a unit variant.
236//!
237//! # Interop checklist
238//!
239//! A typical FFI-exposed function follows this pattern:
240//!
241//! 1. Receive a `*const CInput` from C and convert it with [`AsRust`], or
242//! borrow it with [`RawBorrow`] if the C side keeps ownership.
243//! 2. Run the Rust logic.
244//! 3. Build a `COutput` with [`CReprOf`] and return it to C via
245//! [`RawPointerConverter::into_raw_pointer`].
246//! 4. Expose a `free`-style function that takes the pointer back with
247//! [`RawPointerConverter::from_raw_pointer`] and lets the value drop.
248
249pub use ffi_convert_derive::*;
250
251mod conversions;
252
253pub use conversions::*;