iffi/
lib.rs

1#![no_std]
2#![allow(clippy::drop_non_drop)]
3#![doc = include_str!("../README.md")]
4
5#[cfg(feature = "alloc")]
6extern crate alloc;
7
8#[cfg(feature = "std")]
9extern crate std;
10
11use core::{
12    any::type_name,
13    mem::{size_of, size_of_val},
14};
15
16pub use iffi_macros::{Iffi, Nicheless};
17
18mod niche;
19pub use niche::*;
20
21mod error;
22pub use error::*;
23
24mod impls;
25
26#[cfg_attr(feature = "alloc", path = "alloc_bits.rs")]
27#[cfg_attr(not(feature = "alloc"), path = "nostd_bits.rs")]
28mod bits;
29pub use bits::{BitPattern, BitRanges};
30
31mod maybe_invalid;
32pub use maybe_invalid::*;
33
34/// The core trait of the `iffi` crate.
35///
36/// This is typically implemented by deriving [`Iffi`],
37/// which can be done for all FFI-safe structs and enums.
38///
39/// The set of possible values of `Self` that are well-defined
40/// is a subset of the well-defined values of the `U` type parameter.
41///
42/// The `U` type parameter, called a *[universe]*, must be [`Nicheless`].
43///
44/// # Safety
45/// The implementation must guarantee that `Self` and `U`
46/// have *identical* layouts - the same size, alignment and ABI.
47///
48/// [`can_transmute`] must return not return `Ok(())`
49/// unless `U` can safely be transmuted into `Self`.
50///
51/// [universe]: crate#universe
52/// [`can_transmute`]: [`Iffi::can_transmute`]
53pub unsafe trait Iffi<U: Nicheless = MaybeInvalid<Self>> {
54    /// Checks for the safety of transmuting `U` into `Self`.
55    /// Returns `Ok(())` if the value is safe, and [`Err(iffi::Error)`] otherwise.
56    ///
57    /// [`Err(iffi::Error)`]: [`Error`]
58    fn can_transmute(superset: &U) -> Result<(), Error>;
59}
60
61/// Tries to convert an FFI-safe [nicheless] type to a more ergonomic one.
62///
63/// [nicheless]: Nicheless
64pub fn try_from<T: Iffi<U>, U: Nicheless + core::fmt::Debug>(value: U) -> Result<T, Error> {
65    T::can_transmute(&value)?;
66    debug_assert_eq!(
67        size_of_val(&value),
68        size_of::<T>(),
69        "tried converting from {} to {} but they are different sizes!",
70        type_name::<U>(),
71        type_name::<T>(),
72    );
73    // SAFETY: the superset and the subset are the same size and value is safe to transmute.
74    unsafe { Ok(transmute::transmute(value)) }
75}
76
77/// Converts an ergonomic Rust type to an FFI-safe [nicheless] type.
78///
79/// [nicheless]: Nicheless
80pub fn into<T: Iffi<U>, U: Nicheless>(safe: T) -> U {
81    debug_assert_eq!(
82        size_of::<U>(),
83        size_of_val(&safe),
84        "tried converting {} into {} but they are different sizes!",
85        type_name::<T>(),
86        type_name::<U>(),
87    );
88    // SAFETY: the Iffi trait guarantees that T::Univserse is a superset of T
89    unsafe { transmute::transmute(safe) }
90}
91
92#[cfg(test)]
93mod tests {
94    use core::{
95        marker::PhantomData,
96        num::{NonZeroU32, NonZeroU8},
97    };
98
99    use crate::{self as iffi, *};
100    use iffi::Iffi;
101
102    macro_rules! roundtrip {
103        ($expr:expr) => {
104            assert_eq!(Ok($expr), try_from(into($expr)));
105        };
106    }
107
108    macro_rules! assert_fails {
109        ($ty:ty = $expr:expr => $error:expr) => {
110            let attempt: Result<$ty, _> = try_from($expr);
111            match attempt {
112                Err(Error { error, .. }) => assert_eq!(error, $error),
113                _ => panic!("expected error"),
114            }
115        };
116    }
117
118    #[test]
119    fn conversions_fails() {
120        assert_fails!(NonZeroU8 = 0 => ErrorKind::InvalidBitPattern {
121            bits: BitPattern::from_le(&0u8),
122            valid: BitRanges::from_le(&[1u8..=0xff])
123        });
124
125        assert_fails!(NonZeroU32 = 0 => ErrorKind::InvalidBitPattern {
126            bits: BitPattern::from_le(&[0u8; 4]),
127            valid: BitRanges::from_le(&[1u32..=0xffffffff])
128        });
129    }
130
131    #[test]
132    fn derive_iffi() {
133        #[derive(Iffi)]
134        #[repr(C)]
135        struct A {
136            a: u8,
137            b: NonZeroU8,
138            #[iffi(with = "u8")]
139            c: u8,
140        }
141
142        #[derive(Iffi, Clone, Copy, PartialEq, Debug)]
143        #[repr(isize, align(1024))]
144        enum TA {
145            A,
146            B,
147            D(u32) = 150,
148            E { a: u16, b: NonZeroU8 },
149        }
150
151        roundtrip!(TA::A);
152        roundtrip!(TA::B);
153        roundtrip!(TA::D(5));
154        roundtrip!(TA::E {
155            a: 100,
156            b: NonZeroU8::new(3).unwrap(),
157        });
158    }
159
160    #[test]
161    fn derive_generics() {
162        #[derive(Iffi)]
163        #[repr(C)]
164        struct A<T: Iffi, U> {
165            b: T,
166            a: PhantomData<U>,
167        }
168    }
169
170    #[test]
171    fn nested() {
172        #[derive(Iffi, PartialEq, Debug)]
173        #[repr(C)]
174        struct A;
175
176        #[derive(Iffi, PartialEq, Debug)]
177        #[repr(C)]
178        struct B(A);
179
180        #[derive(Iffi, PartialEq, Debug)]
181        #[repr(C)]
182        struct C {
183            b: B,
184        }
185
186        #[derive(Iffi, PartialEq, Debug)]
187        #[repr(u8)]
188        enum D {
189            A(A),
190            B(B),
191            C(C),
192            D { c: C },
193            E,
194        }
195
196        roundtrip!(A);
197        roundtrip!(B(A));
198        roundtrip!(C { b: B(A) });
199        roundtrip!(D::A(A));
200        roundtrip!(D::B(B(A)));
201        roundtrip!(D::C(C { b: B(A) }));
202        roundtrip!(D::D { c: C { b: B(A) } });
203        roundtrip!(D::E);
204
205        #[derive(Iffi, PartialEq, Debug, Clone, Copy)]
206        #[repr(C)]
207        struct Deep1(NonZeroU8);
208        #[derive(Iffi, PartialEq, Debug, Clone, Copy)]
209        #[repr(C)]
210        struct Deep2(Deep1);
211        #[derive(Iffi, PartialEq, Debug, Clone, Copy)]
212        #[repr(C)]
213        struct Deep3(Deep2);
214        #[derive(Iffi, PartialEq, Debug, Clone, Copy)]
215        #[repr(C)]
216        struct Deep4(Deep3);
217        #[derive(Iffi, PartialEq, Debug, Clone, Copy)]
218        #[repr(C)]
219        struct Deep5(Deep4);
220        #[derive(Iffi, PartialEq, Debug, Clone, Copy)]
221        #[repr(C)]
222        struct Deep6(Deep5);
223        #[derive(Iffi, PartialEq, Debug, Clone, Copy)]
224        #[repr(C)]
225        struct Deep7(Deep6);
226        #[derive(Iffi, PartialEq, Debug, Clone, Copy)]
227        #[repr(C)]
228        struct Deep8(Deep7);
229
230        roundtrip!(Deep8(Deep7(Deep6(Deep5(Deep4(Deep3(Deep2(Deep1(
231            NonZeroU8::new(5).unwrap(),
232        )))))))));
233
234        let invalid: MaybeInvalid<Deep8> = MaybeInvalid::zeroed();
235        let from: Result<Deep8, _> = try_from(invalid);
236        assert_eq!(
237            from,
238            Err(Error {
239                error: ErrorKind::InvalidBitPattern {
240                    bits: BitPattern::from_le(&0u8),
241                    valid: BitRanges::from_le(&[1u8..=0xff])
242                },
243                from: type_name::<MaybeInvalid<NonZeroU8>>(),
244                into: type_name::<NonZeroU8>()
245            })
246        )
247    }
248}