Skip to main content

ext_php_rs_introspection/
abi.rs

1//! ABI-stable standard library types.
2//!
3//! The description module is used by the `cargo-php` sub-command to retrieve
4//! information about the extension. As Rust does not have a stable ABI, it is
5//! not as simple as working in the Rust domain, as if the CLI and extension
6//! Rust versions do not match, it cannot be assumed that the types have the
7//! same memory layout.
8//!
9//! This module contains thin wrappers around standard library types used by the
10//! describe function to provide some sort of ABI-stability.
11//!
12//! As a general rule of thumb, no Rust type is ABI-stable. Strictly speaking,
13//! [`usize`] should not be in use, but rather `size_t` or a similar type,
14//! however these are currently unstable.
15
16use std::{fmt::Display, ops::Deref, vec::Vec as StdVec};
17
18/// An immutable, ABI-stable [`Vec`][std::vec::Vec].
19#[repr(C)]
20#[derive(Debug)]
21pub struct Vec<T> {
22    ptr: *mut T,
23    len: usize,
24}
25
26impl<T> Deref for Vec<T> {
27    type Target = [T];
28
29    fn deref(&self) -> &Self::Target {
30        unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
31    }
32}
33
34impl<T> Drop for Vec<T> {
35    fn drop(&mut self) {
36        unsafe {
37            let _ = Box::from_raw(std::ptr::slice_from_raw_parts_mut(self.ptr, self.len));
38        };
39    }
40}
41
42impl<T> From<StdVec<T>> for Vec<T> {
43    fn from(vec: StdVec<T>) -> Self {
44        let vec = vec.into_boxed_slice();
45        let len = vec.len();
46        let ptr = Box::into_raw(vec).cast::<T>();
47
48        Self { ptr, len }
49    }
50}
51
52impl<T> PartialEq for Vec<T>
53where
54    T: PartialEq,
55{
56    fn eq(&self, other: &Self) -> bool {
57        self.len == other.len && self.as_ref() == other.as_ref()
58    }
59}
60
61/// An immutable, ABI-stable borrowed [`&'static str`][str].
62#[repr(C)]
63#[derive(Debug, Clone, Copy)]
64pub struct Str {
65    ptr: *const u8,
66    len: usize,
67}
68
69impl Str {
70    /// Wraps a static string slice. This function is usable in `const` items.
71    #[must_use]
72    pub const fn new(val: &'static str) -> Self {
73        Self {
74            ptr: val.as_ptr(),
75            len: val.len(),
76        }
77    }
78
79    /// Returns the string as a string slice.
80    ///
81    /// The lifetime is `'static` and can outlive the [`Str`] object, as you can
82    /// only initialize a [`Str`] through a static reference.
83    #[must_use]
84    pub const fn str(&self) -> &'static str {
85        unsafe { std::str::from_utf8_unchecked(std::slice::from_raw_parts(self.ptr, self.len)) }
86    }
87}
88
89impl From<&'static str> for Str {
90    fn from(val: &'static str) -> Self {
91        Self::new(val)
92    }
93}
94
95impl AsRef<str> for Str {
96    fn as_ref(&self) -> &str {
97        self.str()
98    }
99}
100
101impl Display for Str {
102    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103        self.str().fmt(f)
104    }
105}
106
107impl PartialEq for Str {
108    fn eq(&self, other: &Self) -> bool {
109        self.len == other.len && self.str() == other.str()
110    }
111}
112
113impl Eq for Str {}
114
115// SAFETY: `Str` only wraps a `&'static str`, which is `Send` and `Sync`.
116unsafe impl Send for Str {}
117// SAFETY: see the `Send` impl above.
118unsafe impl Sync for Str {}
119
120/// An ABI-stable String
121#[repr(C)]
122#[derive(Debug, PartialEq)]
123pub struct RString {
124    inner: Vec<u8>,
125}
126
127impl RString {
128    /// Returns the string as a string slice.
129    ///
130    /// # Panics
131    ///
132    /// * If the string is not valid UTF-8
133    #[inline]
134    #[must_use]
135    pub fn as_str(&self) -> &str {
136        std::str::from_utf8(&self.inner).expect("RString value is not valid UTF-8")
137    }
138}
139
140impl From<&str> for RString {
141    fn from(s: &str) -> Self {
142        Self {
143            inner: s.as_bytes().to_vec().into(),
144        }
145    }
146}
147
148impl From<String> for RString {
149    fn from(s: String) -> Self {
150        Self {
151            inner: s.into_bytes().into(),
152        }
153    }
154}
155
156impl AsRef<str> for RString {
157    fn as_ref(&self) -> &str {
158        self.as_str()
159    }
160}
161
162impl Display for RString {
163    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164        self.as_str().fmt(f)
165    }
166}
167
168/// An ABI-stable [`Option`][std::option::Option].
169#[repr(C, u8)]
170#[derive(Debug, Clone, Copy)]
171pub enum Option<T> {
172    /// [`Option::Some`][std::option::Option::Some] variant.
173    Some(T),
174    /// [`Option::None`][std::option::Option::None] variant.
175    None,
176}
177
178impl<T> From<std::option::Option<T>> for Option<T> {
179    fn from(opt: std::option::Option<T>) -> Self {
180        match opt {
181            Some(val) => Self::Some(val),
182            None => Self::None,
183        }
184    }
185}
186
187impl<T> PartialEq for Option<T>
188where
189    T: PartialEq,
190{
191    fn eq(&self, other: &Self) -> bool {
192        match (self, other) {
193            (Self::Some(a), Self::Some(b)) => a == b,
194            (Self::None, Self::None) => true,
195            _ => false,
196        }
197    }
198}
199
200impl<T> Eq for Option<T> where T: Eq {}
201
202/// Pins the layout of an ABI type. rustc must accept the type by value in an
203/// `extern "C"` signature, and its size and alignment on 64-bit targets must
204/// match the recorded values. If this fails, the introspection contract
205/// changed and the commit must be marked as breaking.
206macro_rules! assert_ffi_safe {
207    ($t:ty, size = $size:literal, align = $align:literal) => {
208        const _: () = {
209            #[deny(improper_ctypes_definitions)]
210            #[allow(dead_code)]
211            extern "C" fn by_value(_: $t) {}
212            #[cfg(target_pointer_width = "64")]
213            assert!(::std::mem::size_of::<$t>() == $size && ::std::mem::align_of::<$t>() == $align);
214        };
215    };
216}
217pub(crate) use assert_ffi_safe;
218
219assert_ffi_safe!(Vec<u8>, size = 16, align = 8);
220assert_ffi_safe!(Str, size = 16, align = 8);
221assert_ffi_safe!(RString, size = 16, align = 8);
222assert_ffi_safe!(Option<Str>, size = 24, align = 8);