vulkanalia_sys/
arrays.rs

1// SPDX-License-Identifier: Apache-2.0
2
3#![allow(
4    non_camel_case_types,
5    non_snake_case,
6    clippy::missing_safety_doc,
7    clippy::too_many_arguments,
8    clippy::type_complexity,
9    clippy::upper_case_acronyms
10)]
11
12use alloc::borrow::Cow;
13use alloc::string::String;
14use core::ffi::{CStr, c_char};
15use core::fmt;
16use core::hash;
17use core::ops;
18
19/// An array containing a sequence of bytes.
20#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
21#[repr(transparent)]
22pub struct ByteArray<const N: usize>(pub [u8; N]);
23
24impl<const N: usize> Default for ByteArray<N> {
25    #[inline]
26    fn default() -> Self {
27        Self([0; N])
28    }
29}
30
31impl<const N: usize> ops::Deref for ByteArray<N> {
32    type Target = [u8; N];
33
34    #[inline]
35    fn deref(&self) -> &Self::Target {
36        &self.0
37    }
38}
39
40impl<const N: usize> fmt::Debug for ByteArray<N> {
41    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
42        write!(f, "ByteArray<{}>({:?})", N, self.0)
43    }
44}
45
46impl<const N: usize> fmt::Display for ByteArray<N> {
47    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
48        write!(f, "{:?}", self.0)
49    }
50}
51
52impl<const N: usize> From<[u8; N]> for ByteArray<N> {
53    #[inline]
54    fn from(array: [u8; N]) -> Self {
55        Self(array)
56    }
57}
58
59impl<const N: usize> From<ByteArray<N>> for [u8; N] {
60    #[inline]
61    fn from(array: ByteArray<N>) -> Self {
62        array.0
63    }
64}
65
66/// An array containing a null-terminated string.
67///
68/// # Equality / Hashing
69///
70/// For the purposes of comparing and hashing array strings, any characters
71/// after the first null terminator are ignored. The below example demonstrates
72/// this property with two strings that differ in the characters that come
73/// after the first null terminators.
74///
75/// ```
76/// # use std::collections::hash_map::DefaultHasher;
77/// # use std::hash::{Hash, Hasher};
78/// # use vulkanalia_sys::StringArray;
79/// let string1 = StringArray::<3>::new([0x61, 0, 0]);
80/// let string2 = StringArray::<3>::new([0x61, 0, 0x61]);
81///
82/// assert_eq!(string1, string2);
83///
84/// let mut hasher1 = DefaultHasher::new();
85/// string1.hash(&mut hasher1);
86/// let mut hasher2 = DefaultHasher::new();
87/// string2.hash(&mut hasher2);
88///
89/// assert_eq!(hasher1.finish(), hasher2.finish());
90/// ```
91#[derive(Copy, Clone, PartialOrd, Ord)]
92#[repr(transparent)]
93pub struct StringArray<const N: usize>([c_char; N]);
94
95impl<const N: usize> StringArray<N> {
96    /// Constructs a string array from a character array.
97    ///
98    /// # Panics
99    ///
100    /// * `characters` does not contain a null-terminator
101    #[inline]
102    pub fn new(array: [c_char; N]) -> Self {
103        assert!(array.contains(&0));
104        Self(array)
105    }
106
107    /// Constructs a string array from a byte string.
108    ///
109    /// If the byte string is longer than `N - 1`, then the byte string will
110    /// be truncated to fit inside of the constructed string array (the last
111    /// character is reserved for a null terminator). The constructed string
112    /// array will always be null-terminated regardless if the byte string is
113    /// or is not null-terminated.
114    #[inline]
115    pub const fn from_bytes(bytes: &[u8]) -> Self {
116        let mut array = [0; N];
117
118        let mut index = 0;
119        while index < bytes.len() && index + 1 < N {
120            if bytes[index] != 0 {
121                array[index] = bytes[index] as c_char;
122                index += 1;
123            } else {
124                break;
125            }
126        }
127
128        Self(array)
129    }
130
131    /// Constructs a string array from a borrowed C string.
132    ///
133    /// If the borrowed C string is longer than `N - 1`, then the borrowed C
134    /// string will be truncated to fit inside of the constructed string array
135    /// (the last character is reserved for a null terminator).
136    #[inline]
137    pub fn from_cstr(cstr: &CStr) -> Self {
138        Self::from_bytes(cstr.to_bytes())
139    }
140
141    /// Constructs a string array from a pointer to a null-terminated string.
142    ///
143    /// If the null-terminated string is longer than `N - 1`, then the
144    /// null-terminated string will be truncated to fit inside of the
145    /// constructed string array (the last character is reserved for a null
146    /// terminator).
147    ///
148    /// # Safety
149    ///
150    /// * `ptr` must be a pointer to a null-terminated string
151    #[inline]
152    pub unsafe fn from_ptr(ptr: *const c_char) -> Self {
153        Self::from_cstr(unsafe { CStr::from_ptr(ptr) })
154    }
155
156    /// Gets the underlying character array for this string array.
157    #[inline]
158    pub fn as_array(&self) -> &[c_char; N] {
159        &self.0
160    }
161
162    /// Gets this string array as a slice of bytes.
163    #[inline]
164    pub fn as_bytes(&self) -> &[u8] {
165        unsafe { self.as_array().align_to::<u8>().1 }
166    }
167
168    /// Gets this string array as a borrowed C string.
169    #[inline]
170    pub fn as_cstr(&self) -> &CStr {
171        let bytes = self.as_bytes();
172        let nul = bytes.iter().position(|b| *b == b'\0');
173        let end = nul.unwrap_or(N - 1);
174        unsafe { CStr::from_bytes_with_nul_unchecked(&bytes[..end + 1]) }
175    }
176
177    /// Converts this string array to a UTF-8 string (lossily).
178    #[inline]
179    pub fn to_string_lossy(&self) -> Cow<'_, str> {
180        let bytes = self.as_bytes();
181        let nul = bytes.iter().position(|b| *b == b'\0');
182        let end = nul.unwrap_or(N);
183        String::from_utf8_lossy(&bytes[..end])
184    }
185}
186
187impl<const N: usize> Default for StringArray<N> {
188    #[inline]
189    fn default() -> Self {
190        Self([0; N])
191    }
192}
193
194impl<const N: usize> PartialEq for StringArray<N> {
195    fn eq(&self, other: &Self) -> bool {
196        self.as_cstr() == other.as_cstr()
197    }
198}
199
200impl<const N: usize> Eq for StringArray<N> {}
201
202impl<const N: usize> hash::Hash for StringArray<N> {
203    fn hash<H: hash::Hasher>(&self, hasher: &mut H) {
204        self.as_cstr().hash(hasher);
205    }
206}
207
208impl<const N: usize> ops::Deref for StringArray<N> {
209    type Target = [c_char; N];
210
211    #[inline]
212    fn deref(&self) -> &Self::Target {
213        &self.0
214    }
215}
216
217impl<const N: usize> fmt::Debug for StringArray<N> {
218    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
219        write!(f, "StringArray<{}>({:?})", N, self.to_string_lossy())
220    }
221}
222
223impl<const N: usize> fmt::Display for StringArray<N> {
224    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
225        write!(f, "{}", self.to_string_lossy())
226    }
227}
228
229impl<const N: usize> From<[c_char; N]> for StringArray<N> {
230    #[inline]
231    fn from(array: [c_char; N]) -> Self {
232        Self(array)
233    }
234}
235
236impl<const N: usize> From<StringArray<N>> for [c_char; N] {
237    #[inline]
238    fn from(array: StringArray<N>) -> Self {
239        array.0
240    }
241}
242
243#[cfg(test)]
244mod test {
245    use super::*;
246
247    use std::collections::hash_map::DefaultHasher;
248    use std::hash::{Hash, Hasher};
249
250    fn hash(hash: impl Hash) -> u64 {
251        let mut hasher = DefaultHasher::new();
252        hash.hash(&mut hasher);
253        hasher.finish()
254    }
255
256    #[test]
257    fn test_string_array_from_bytes() {
258        type S1 = StringArray<1>;
259
260        assert_eq!(b"\0", S1::from_bytes(b"").as_bytes());
261        assert_eq!(b"\0", S1::from_bytes(b"\0").as_bytes());
262        assert_eq!(b"\0", S1::from_bytes(b"\0bar").as_bytes());
263
264        assert_eq!(b"\0", S1::from_bytes(b"322").as_bytes());
265        assert_eq!(b"\0", S1::from_bytes(b"322\0").as_bytes());
266        assert_eq!(b"\0", S1::from_bytes(b"322\0bar").as_bytes());
267
268        type S4 = StringArray<4>;
269
270        assert_eq!(b"\0\0\0\0", S4::from_bytes(b"").as_bytes());
271        assert_eq!(b"\0\0\0\0", S4::from_bytes(b"\0").as_bytes());
272        assert_eq!(b"\0\0\0\0", S4::from_bytes(b"\0bar").as_bytes());
273
274        assert_eq!(b"322\0", S4::from_bytes(b"322").as_bytes());
275        assert_eq!(b"322\0", S4::from_bytes(b"322\0").as_bytes());
276        assert_eq!(b"322\0", S4::from_bytes(b"322\0bar").as_bytes());
277
278        assert_eq!(b"128\0", S4::from_bytes(b"1288").as_bytes());
279        assert_eq!(b"128\0", S4::from_bytes(b"1288\0").as_bytes());
280        assert_eq!(b"128\0", S4::from_bytes(b"1288\0bar").as_bytes());
281    }
282
283    #[test]
284    fn test_string_array_cmp() {
285        macro_rules! assert_cmp_eq {
286            ($left:expr, $right:expr) => {
287                assert_eq!($left, $right);
288                assert_eq!(hash($left), hash($right));
289            };
290        }
291
292        macro_rules! assert_cmp_ne {
293            ($left:expr, $right:expr) => {
294                assert_ne!($left, $right);
295                assert_ne!(hash($left), hash($right));
296            };
297        }
298
299        type S32 = StringArray<32>;
300
301        assert_cmp_eq!(S32::from_bytes(b""), S32::from_bytes(b""));
302        assert_cmp_eq!(S32::from_bytes(b"\0"), S32::from_bytes(b""));
303        assert_cmp_eq!(S32::from_bytes(b""), S32::from_bytes(b"\0"));
304        assert_cmp_eq!(S32::from_bytes(b"\0"), S32::from_bytes(b"\0"));
305        assert_cmp_eq!(S32::from_bytes(b"\0foo"), S32::from_bytes(b"\0bar"));
306
307        assert_cmp_eq!(S32::from_bytes(b"322"), S32::from_bytes(b"322"));
308        assert_cmp_eq!(S32::from_bytes(b"322\0"), S32::from_bytes(b"322"));
309        assert_cmp_eq!(S32::from_bytes(b"322"), S32::from_bytes(b"322\0"));
310        assert_cmp_eq!(S32::from_bytes(b"322\0"), S32::from_bytes(b"322\0"));
311        assert_cmp_eq!(S32::from_bytes(b"322\0foo"), S32::from_bytes(b"322\0bar"));
312
313        assert_cmp_ne!(S32::from_bytes(b"322"), S32::from_bytes(b"422"));
314        assert_cmp_ne!(S32::from_bytes(b"322"), S32::from_bytes(b"332"));
315        assert_cmp_ne!(S32::from_bytes(b"322"), S32::from_bytes(b"323"));
316
317        assert_cmp_ne!(S32::from_bytes(b"322"), S32::from_bytes(b"32"));
318        assert_cmp_ne!(S32::from_bytes(b"322"), S32::from_bytes(b"3222"));
319    }
320}