ffi_destruct/lib.rs
1//! # FFI Destruct
2//! Generates destructors for structures that contain raw pointers in the FFI.
3//!
4//! ## Example
5//! Provides a structure with several raw pointers that need to be dropped manually.
6//! ```
7//! use ffi_destruct::{extern_c_destructor, Destruct};
8//! use std::ffi::*;
9//!
10//! #[derive(Destruct)]
11//! pub struct MyStruct {
12//! field: *mut std::ffi::c_char,
13//! }
14//!
15//! pub struct AnyOther(u32, u32);
16//!
17//! // Struct definition here, with deriving Destruct and nullable attributes.
18//! #[derive(Destruct)]
19//! pub struct Structure {
20//! // Default is non-null.
21//! c_string: *const c_char,
22//! #[nullable]
23//! c_string_nullable: *mut c_char,
24//!
25//! other: *mut MyStruct,
26//! #[nullable]
27//! other_nullable: *mut MyStruct,
28//!
29//! // Do not drop this field.
30//! #[no_drop]
31//! not_dropped: *const AnyOther,
32//!
33//! // Raw pointer for any other things
34//! any: *mut AnyOther,
35//!
36//! // Non-pointer types are still available, and will not be added to drop().
37//! pub normal_int: u32,
38//! pub normal_string: String,
39//! }
40//!
41//! // (Optional) The macro here generates the destructor: destruct_structure()
42//! extern_c_destructor!(Structure);
43//!
44//! fn test() {
45//! // Some resources manually managed
46//! let tmp = AnyOther(1, 1);
47//! let tmp_ptr = Box::into_raw(Box::new(tmp));
48//!
49//! let my_struct = Structure {
50//! c_string: CString::new("Hello").unwrap().into_raw(),
51//! c_string_nullable: std::ptr::null_mut(),
52//! other: Box::into_raw(Box::new(MyStruct {
53//! field: CString::new("Hello").unwrap().into_raw(),
54//! })),
55//! other_nullable: std::ptr::null_mut(),
56//! not_dropped: tmp_ptr,
57//! any: Box::into_raw(Box::new(AnyOther(1, 1))),
58//! normal_int: 114514,
59//! normal_string: "Hello".to_string(),
60//! };
61//!
62//! let my_struct_ptr = Box::into_raw(Box::new(my_struct));
63//! // FFI calling
64//! unsafe {
65//! destruct_structure(my_struct_ptr);
66//! }
67//!
68//! // Drop the manually managed resources
69//! unsafe {
70//! let _ = Box::from_raw(tmp_ptr);
71//! }
72//! }
73//! ```
74//!
75//! After expanding the macros:
76//! ```ignore
77//! #[macro_use]
78//! #![feature(prelude_import)]
79//! #![allow(dead_code)]
80//! #[prelude_import]
81//! use std::prelude::rust_2021::*;
82//! #[macro_use]
83//! extern crate std;
84//! use ffi_destruct::{extern_c_destructor, Destruct};
85//! use std::ffi::*;
86//! pub struct MyStruct {
87//! field: *mut std::ffi::c_char,
88//! }
89//! impl ::std::ops::Drop for MyStruct {
90//! fn drop(&mut self) {
91//! unsafe {
92//! let _ = ::std::ffi::CString::from_raw(self.field as *mut ::std::ffi::c_char);
93//! }
94//! }
95//! }
96//! pub struct AnyOther(u32, u32);
97//! pub struct Structure {
98//! c_string: *const c_char,
99//! #[nullable]
100//! c_string_nullable: *mut c_char,
101//! other: *mut MyStruct,
102//! #[nullable]
103//! other_nullable: *mut MyStruct,
104//! #[no_drop]
105//! not_dropped: *const AnyOther,
106//! any: *mut AnyOther,
107//! pub normal_int: u32,
108//! pub normal_string: String,
109//! }
110//! impl ::std::ops::Drop for Structure {
111//! fn drop(&mut self) {
112//! unsafe {
113//! let _ = ::std::ffi::CString::from_raw(
114//! self.c_string as *mut ::std::ffi::c_char,
115//! );
116//! if !self.c_string_nullable.is_null() {
117//! let _ = ::std::ffi::CString::from_raw(
118//! self.c_string_nullable as *mut ::std::ffi::c_char,
119//! );
120//! }
121//! let _ = ::std::boxed::Box::from_raw(self.other as *mut MyStruct);
122//! if !self.other_nullable.is_null() {
123//! let _ = ::std::boxed::Box::from_raw(
124//! self.other_nullable as *mut MyStruct,
125//! );
126//! }
127//! let _ = ::std::boxed::Box::from_raw(self.any as *mut AnyOther);
128//! }
129//! }
130//! }
131//! #[no_mangle]
132//! pub unsafe extern "C" fn destruct_structure(ptr: *mut Structure) {
133//! if ptr.is_null() {
134//! return;
135//! }
136//! let _ = ::std::boxed::Box::from_raw(ptr);
137//! }
138//! fn test() {
139//! let tmp = AnyOther(1, 1);
140//! let tmp_ptr = Box::into_raw(Box::new(tmp));
141//! let my_struct = Structure {
142//! c_string: CString::new("Hello").unwrap().into_raw(),
143//! c_string_nullable: std::ptr::null_mut(),
144//! other: Box::into_raw(
145//! Box::new(MyStruct {
146//! field: CString::new("Hello").unwrap().into_raw(),
147//! }),
148//! ),
149//! other_nullable: std::ptr::null_mut(),
150//! not_dropped: tmp_ptr,
151//! any: Box::into_raw(Box::new(AnyOther(1, 1))),
152//! normal_int: 114514,
153//! normal_string: "Hello".to_string(),
154//! };
155//! let my_struct_ptr = Box::into_raw(Box::new(my_struct));
156//! unsafe {
157//! destruct_structure(my_struct_ptr);
158//! }
159//! unsafe {
160//! let _ = Box::from_raw(tmp_ptr);
161//! }
162//! }
163//! ```
164
165mod destruct;
166mod utils;
167
168use convert_case::{Case, Casing};
169use proc_macro2::{Ident, TokenStream};
170use quote::{quote, quote_spanned, ToTokens};
171use syn::{parse_macro_input, spanned::Spanned, Data, DeriveInput};
172
173/// The [`Destruct`] derive macro.
174///
175/// Generate a destructor for the structure.
176///
177/// ## Field Attributes
178/// - `#[nullable]` - The field is nullable, the destructor will check if the pointer is null before
179/// - `#[no_drop]` - The field will not be added to the destructor
180#[proc_macro_derive(Destruct, attributes(nullable, no_drop))]
181pub fn destruct_macro_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
182 let input = parse_macro_input!(input as DeriveInput);
183
184 let expand = destruct::impl_destruct_macro(&input);
185
186 proc_macro::TokenStream::from(expand)
187}
188
189/// Generate extern "C" destructor for provide type
190///
191/// Provide the function name: "destruct_" + snake_case name of the type.
192///
193/// ## Usage
194///
195/// ```
196/// // Definition of struct here
197/// # use ffi_destruct::{Destruct, extern_c_destructor};
198/// #[derive(Destruct)]
199/// pub struct MyStruct {
200/// field: *mut std::ffi::c_char,
201/// }
202/// // destructor macro here
203/// extern_c_destructor!(MyStruct);
204/// ```
205/// The macro will be expanded to:
206/// ```
207/// # use ffi_destruct::Destruct;
208/// # #[derive(Destruct)]
209/// # pub struct MyStruct {
210/// # field: *mut std::ffi::c_char,
211/// # }
212/// #[no_mangle]
213/// pub unsafe extern "C" fn destruct_my_struct(ptr: *mut MyStruct) {
214/// if ptr.is_null() {
215/// return;
216/// }
217/// let _ = ::std::boxed::Box::from_raw(ptr);
218/// }
219/// ```
220#[proc_macro]
221pub fn extern_c_destructor(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
222 let ty: syn::Type = parse_macro_input!(input);
223 match ty {
224 syn::Type::Path(v) => {
225 let ident = v.path.get_ident().expect("Only support single ident.");
226 let mut name = ident.to_string().to_case(Case::Snake);
227 name.insert_str(0, "destruct_");
228 let fn_ident = Ident::new(&name, ident.span());
229 quote! {
230 #[no_mangle]
231 pub unsafe extern "C" fn #fn_ident(ptr: *mut #ident) {
232 if ptr.is_null() {
233 return;
234 }
235 let _ = ::std::boxed::Box::from_raw(ptr);
236 }
237 }
238 .into()
239 }
240 _ => panic!("Not supported type"),
241 }
242}