Skip to main content

vulkan_rust_sys/
string_array.rs

1//! Fixed-size null-terminated string wrapper for Vulkan `[c_char; N]` fields.
2//!
3//! [`StringArray<N>`] is a `#[repr(transparent)]` wrapper around `[c_char; N]`
4//! that provides `Display`, `Debug`, `PartialEq`, and `Hash` implementations
5//! which compare only up to the first null terminator. This makes it safe to
6//! compare extension names, layer names, and device names directly.
7
8use core::ffi::{c_char, CStr};
9use core::{fmt, hash};
10
11use crate::constants::{
12    MAX_DESCRIPTION_SIZE, MAX_DRIVER_INFO_SIZE, MAX_DRIVER_NAME_SIZE, MAX_EXTENSION_NAME_SIZE,
13    MAX_PHYSICAL_DEVICE_NAME_SIZE,
14};
15
16/// Extension name string, e.g. from `ExtensionProperties::extension_name`.
17pub type ExtensionName = StringArray<{ MAX_EXTENSION_NAME_SIZE as usize }>;
18
19/// Layer name string, e.g. from `LayerProperties::layer_name`.
20pub type LayerName = StringArray<{ MAX_EXTENSION_NAME_SIZE as usize }>;
21
22/// Physical device name string, from `PhysicalDeviceProperties::device_name`.
23pub type DeviceName = StringArray<{ MAX_PHYSICAL_DEVICE_NAME_SIZE as usize }>;
24
25/// Description string, from `LayerProperties::description`.
26pub type DescriptionName = StringArray<{ MAX_DESCRIPTION_SIZE as usize }>;
27
28/// Driver name string, from `PhysicalDeviceDriverProperties::driver_name`.
29pub type DriverName = StringArray<{ MAX_DRIVER_NAME_SIZE as usize }>;
30
31/// Driver info string, from `PhysicalDeviceDriverProperties::driver_info`.
32pub type DriverInfo = StringArray<{ MAX_DRIVER_INFO_SIZE as usize }>;
33
34/// A fixed-size array containing a null-terminated C string.
35///
36/// Wraps the raw `[c_char; N]` arrays used by Vulkan structs
37/// (e.g. `ExtensionProperties::extension_name`). Equality and hashing
38/// ignore bytes after the first null terminator.
39///
40/// # Examples
41///
42/// ```no_run
43/// use vulkan_rust_sys::StringArray;
44///
45/// let a = StringArray::<256>::from_cstr(c"VK_KHR_swapchain");
46/// let b = StringArray::<256>::from_cstr(c"VK_KHR_swapchain");
47/// assert_eq!(a, b);
48/// ```
49#[derive(Copy, Clone)]
50#[repr(transparent)]
51pub struct StringArray<const N: usize>(pub [c_char; N]);
52
53impl<const N: usize> StringArray<N> {
54    /// View the contents as a `&CStr`.
55    ///
56    /// If the array contains no null terminator (shouldn't happen with
57    /// well-formed Vulkan data), treats the entire array as the string.
58    #[inline]
59    pub fn as_cstr(&self) -> &CStr {
60        let bytes: &[u8] = unsafe { core::slice::from_raw_parts(self.0.as_ptr().cast(), N) };
61        let end = bytes.iter().position(|&b| b == 0).unwrap_or(N.saturating_sub(1));
62        unsafe { CStr::from_bytes_with_nul_unchecked(&bytes[..end + 1]) }
63    }
64
65    /// Construct from a `&CStr`, truncating if longer than `N - 1`.
66    pub fn from_cstr(cstr: &CStr) -> Self {
67        let mut array = [0 as c_char; N];
68        let bytes = cstr.to_bytes();
69        let len = bytes.len().min(N.saturating_sub(1));
70        for i in 0..len {
71            array[i] = bytes[i] as c_char;
72        }
73        Self(array)
74    }
75}
76
77impl<const N: usize> core::ops::Deref for StringArray<N> {
78    type Target = [c_char; N];
79
80    #[inline]
81    fn deref(&self) -> &Self::Target {
82        &self.0
83    }
84}
85
86impl<const N: usize> Default for StringArray<N> {
87    #[inline]
88    fn default() -> Self {
89        Self([0; N])
90    }
91}
92
93impl<const N: usize> PartialEq for StringArray<N> {
94    fn eq(&self, other: &Self) -> bool {
95        self.as_cstr() == other.as_cstr()
96    }
97}
98
99impl<const N: usize> Eq for StringArray<N> {}
100
101impl<const N: usize> hash::Hash for StringArray<N> {
102    fn hash<H: hash::Hasher>(&self, hasher: &mut H) {
103        self.as_cstr().hash(hasher);
104    }
105}
106
107impl<const N: usize> PartialEq<&CStr> for StringArray<N> {
108    fn eq(&self, other: &&CStr) -> bool {
109        self.as_cstr() == *other
110    }
111}
112
113impl<const N: usize> fmt::Display for StringArray<N> {
114    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115        match self.as_cstr().to_str() {
116            Ok(s) => f.write_str(s),
117            Err(_) => write!(f, "{:?}", self.as_cstr()),
118        }
119    }
120}
121
122impl<const N: usize> fmt::Debug for StringArray<N> {
123    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124        write!(f, "\"{}\"", self)
125    }
126}
127
128impl<const N: usize> From<[c_char; N]> for StringArray<N> {
129    #[inline]
130    fn from(array: [c_char; N]) -> Self {
131        Self(array)
132    }
133}
134
135impl<const N: usize> From<StringArray<N>> for [c_char; N] {
136    #[inline]
137    fn from(array: StringArray<N>) -> Self {
138        array.0
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    extern crate alloc;
145    use alloc::format;
146    use super::*;
147    use core::hash::Hasher;
148
149    fn hash_of(v: impl hash::Hash) -> u64 {
150        let mut h = SimpleHasher(0);
151        v.hash(&mut h);
152        h.0
153    }
154
155    // Minimal hasher for no_std tests.
156    struct SimpleHasher(u64);
157    impl Hasher for SimpleHasher {
158        fn write(&mut self, bytes: &[u8]) {
159            for &b in bytes {
160                self.0 = self.0.wrapping_mul(31).wrapping_add(b as u64);
161            }
162        }
163        fn finish(&self) -> u64 {
164            self.0
165        }
166    }
167
168    #[test]
169    fn equal_strings_are_equal() {
170        let a = StringArray::<256>::from_cstr(c"VK_KHR_swapchain");
171        let b = StringArray::<256>::from_cstr(c"VK_KHR_swapchain");
172        assert_eq!(a, b);
173        assert_eq!(hash_of(a), hash_of(b));
174    }
175
176    #[test]
177    fn different_strings_are_not_equal() {
178        let a = StringArray::<256>::from_cstr(c"VK_KHR_swapchain");
179        let b = StringArray::<256>::from_cstr(c"VK_KHR_surface");
180        assert_ne!(a, b);
181    }
182
183    #[test]
184    fn trailing_garbage_ignored() {
185        let mut arr_a = [0 as c_char; 8];
186        let mut arr_b = [0 as c_char; 8];
187        arr_a[0] = b'a' as c_char;
188        arr_a[1] = 0;
189        arr_a[2] = b'X' as c_char;
190        arr_b[0] = b'a' as c_char;
191        arr_b[1] = 0;
192        arr_b[2] = b'Y' as c_char;
193        assert_eq!(StringArray(arr_a), StringArray(arr_b));
194        assert_eq!(hash_of(StringArray(arr_a)), hash_of(StringArray(arr_b)));
195    }
196
197    #[test]
198    fn compare_with_cstr() {
199        let a = StringArray::<256>::from_cstr(c"VK_KHR_swapchain");
200        assert_eq!(a, c"VK_KHR_swapchain");
201    }
202
203    #[test]
204    fn display_shows_string() {
205        let a = StringArray::<256>::from_cstr(c"hello");
206        let s = format!("{a}");
207        assert_eq!(s, "hello");
208    }
209
210    #[test]
211    fn default_is_empty() {
212        let a = StringArray::<32>::default();
213        assert_eq!(a, c"");
214    }
215
216    #[test]
217    fn from_cstr_truncates_long_string() {
218        let a = StringArray::<4>::from_cstr(c"abcdef");
219        assert_eq!(a.as_cstr(), c"abc");
220    }
221
222    #[test]
223    fn round_trip_array() {
224        let orig = [b'h' as c_char, b'i' as c_char, 0, 0];
225        let sa = StringArray::from(orig);
226        let back: [c_char; 4] = sa.into();
227        assert_eq!(orig, back);
228    }
229}