Skip to main content

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::*;