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
34pub unsafe trait Iffi<U: Nicheless = MaybeInvalid<Self>> {
54 fn can_transmute(superset: &U) -> Result<(), Error>;
59}
60
61pub 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 unsafe { Ok(transmute::transmute(value)) }
75}
76
77pub 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 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}