1use std::borrow::{Borrow, Cow};
2use std::ffi::{CStr, CString};
3use std::mem::MaybeUninit;
4use std::os::raw::c_char;
5use std::ptr;
6use std::{fmt, ops};
7
8const STRING_SIZE: usize = 512;
9
10#[allow(clippy::large_enum_variant)]
17pub enum CFixedString {
18 Local {
19 s: [c_char; STRING_SIZE],
20 len: usize,
21 },
22 Heap {
23 s: CString,
24 len: usize,
25 },
26}
27
28impl Default for CFixedString {
29 fn default() -> Self {
30 Self::new()
31 }
32}
33
34impl CFixedString {
35 pub fn new() -> Self {
38 let data: [MaybeUninit<c_char>; STRING_SIZE] =
39 unsafe { MaybeUninit::uninit().assume_init() };
40
41 CFixedString::Local {
42 s: unsafe { std::mem::transmute::<[MaybeUninit<c_char>; STRING_SIZE], [c_char; STRING_SIZE]>(data) },
43 len: 0,
44 }
45 }
46
47 #[allow(clippy::should_implement_trait)]
49 pub fn from_str<S: AsRef<str>>(s: S) -> Self {
50 Self::from(s.as_ref())
51 }
52
53 pub fn as_ptr(&self) -> *const c_char {
55 match *self {
56 CFixedString::Local { ref s, .. } => s.as_ptr(),
57 CFixedString::Heap { ref s, .. } => s.as_ptr(),
58 }
59 }
60
61 pub fn is_allocated(&self) -> bool {
63 !matches!(*self, CFixedString::Local { .. })
64 }
65
66 pub fn to_string(&self) -> Cow<'_, str> {
74 String::from_utf8_lossy(self.to_bytes())
75 }
76
77 pub unsafe fn as_str(&self) -> &str {
85 use std::slice;
86 use std::str;
87
88 match *self {
89 CFixedString::Local { ref s, len } => {
90 str::from_utf8_unchecked(slice::from_raw_parts(s.as_ptr() as *const u8, len))
91 }
92 CFixedString::Heap { ref s, len } => {
93 str::from_utf8_unchecked(slice::from_raw_parts(s.as_ptr() as *const u8, len))
94 }
95 }
96 }
97}
98
99impl<'a> From<&'a str> for CFixedString {
100 fn from(s: &'a str) -> Self {
101 use std::fmt::Write;
102
103 let mut string = CFixedString::new();
104 string.write_str(s).unwrap();
105 string
106 }
107}
108
109impl fmt::Write for CFixedString {
110 fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
111 unsafe {
112 let cur_len = self.as_str().len();
113
114 match cur_len + s.len() {
115 len if len < STRING_SIZE => match *self {
116 CFixedString::Local {
117 s: ref mut ls,
118 len: ref mut lslen,
119 } => {
120 let ptr = ls.as_mut_ptr() as *mut u8;
121 ptr::copy(s.as_ptr(), ptr.add(cur_len), s.len());
122 *ptr.add(len) = 0;
123 *lslen = len;
124 }
125 _ => unreachable!(),
126 },
127 len => {
128 let mut heapstring = String::with_capacity(len + 1);
129
130 heapstring.write_str(self.as_str())?;
131 heapstring.write_str(s)?;
132
133 *self = CFixedString::Heap {
134 s: CString::new(heapstring).unwrap(),
135 len,
136 };
137 }
138 }
139 }
140
141 Ok(())
142 }
143}
144
145impl From<CFixedString> for String {
146 fn from(s: CFixedString) -> Self {
147 String::from_utf8_lossy(s.to_bytes()).into_owned()
148 }
149}
150
151impl ops::Deref for CFixedString {
152 type Target = CStr;
153
154 fn deref(&self) -> &CStr {
155 use std::slice;
156
157 match *self {
158 CFixedString::Local { ref s, len } => unsafe {
159 let bytes = slice::from_raw_parts(s.as_ptr() as *const u8, len + 1);
160 CStr::from_bytes_with_nul_unchecked(bytes)
161 },
162 CFixedString::Heap { ref s, .. } => s,
163 }
164 }
165}
166
167impl Borrow<CStr> for CFixedString {
168 fn borrow(&self) -> &CStr {
169 self
170 }
171}
172
173impl AsRef<CStr> for CFixedString {
174 fn as_ref(&self) -> &CStr {
175 self
176 }
177}
178
179impl Borrow<str> for CFixedString {
180 fn borrow(&self) -> &str {
181 unsafe { self.as_str() }
182 }
183}
184
185impl AsRef<str> for CFixedString {
186 fn as_ref(&self) -> &str {
187 unsafe { self.as_str() }
188 }
189}
190
191#[macro_export]
192macro_rules! format_c {
193 ($fmt:expr, $($args:tt)*) => ({
194 use std::fmt::Write;
195
196 let mut fixed = CFixedString::new();
197 write!(&mut fixed, $fmt, $($args)*).unwrap();
198 fixed
199 })
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205 use std::fmt::Write;
206
207 fn gen_string(len: usize) -> String {
208 let mut out = String::with_capacity(len);
209
210 for _ in 0..len / 16 {
211 out.write_str("zyxvutabcdef9876").unwrap();
212 }
213
214 for i in 0..len % 16 {
215 out.write_char(char::from(b'A' + i as u8)).unwrap();
216 }
217
218 assert_eq!(out.len(), len);
219 out
220 }
221
222 #[test]
223 fn test_empty_handler() {
224 let short_string = "";
225
226 let t = CFixedString::from_str(short_string);
227
228 assert!(!t.is_allocated());
229 assert_eq!(&t.to_string(), short_string);
230 }
231
232 #[test]
233 fn test_short_1() {
234 let short_string = "test_local";
235
236 let t = CFixedString::from_str(short_string);
237
238 assert!(!t.is_allocated());
239 assert_eq!(&t.to_string(), short_string);
240 }
241
242 #[test]
243 fn test_short_2() {
244 let short_string = "test_local stoheusthsotheost";
245
246 let t = CFixedString::from_str(short_string);
247
248 assert!(!t.is_allocated());
249 assert_eq!(&t.to_string(), short_string);
250 }
251
252 #[test]
253 fn test_511() {
254 let test_511_string = gen_string(511);
256
257 let t = CFixedString::from_str(&test_511_string);
258
259 assert!(!t.is_allocated());
260 assert_eq!(&t.to_string(), &test_511_string);
261 }
262
263 #[test]
264 fn test_512() {
265 let test_512_string = gen_string(512);
267
268 let t = CFixedString::from_str(&test_512_string);
269
270 assert!(t.is_allocated());
271 assert_eq!(&t.to_string(), &test_512_string);
272 }
273
274 #[test]
275 fn test_513() {
276 let test_513_string = gen_string(513);
278
279 let t = CFixedString::from_str(&test_513_string);
280
281 assert!(t.is_allocated());
282 assert_eq!(&t.to_string(), &test_513_string);
283 }
284
285 #[test]
286 fn test_to_owned() {
287 let short = "this is an amazing string";
288
289 let t = CFixedString::from_str(short);
290
291 assert!(!t.is_allocated());
292 assert_eq!(&String::from(t), short);
293
294 let long = gen_string(1025);
295
296 let t = CFixedString::from_str(&long);
297
298 assert!(t.is_allocated());
299 assert_eq!(&String::from(t), &long);
300 }
301
302 #[test]
303 fn test_short_format() {
304 let mut fixed = CFixedString::new();
305
306 write!(&mut fixed, "one_{}", 1).unwrap();
307 write!(&mut fixed, "_two_{}", "two").unwrap();
308 write!(
309 &mut fixed,
310 "_three_{}-{}-{:.3}",
311 23, "some string data", 56.789
312 )
313 .unwrap();
314
315 assert!(!fixed.is_allocated());
316 assert_eq!(
317 &fixed.to_string(),
318 "one_1_two_two_three_23-some string data-56.789"
319 );
320 }
321
322 #[test]
323 fn test_long_format() {
324 let mut fixed = CFixedString::new();
325 let mut string = String::new();
326
327 for i in 1..30 {
328 let genned = gen_string(i * i);
329
330 write!(&mut fixed, "{}_{}", i, genned).unwrap();
331 write!(&mut string, "{}_{}", i, genned).unwrap();
332 }
333
334 assert!(fixed.is_allocated());
335 assert_eq!(&fixed.to_string(), &string);
336 }
337
338 #[test]
339 fn test_short_fmt_macro() {
340 let first = 23;
341 let second = "#@!*()&^%_-+={}[]|\\/?><,.:;~`";
342 let third = u32::MAX;
343 let fourth = gen_string(512 - 45);
344
345 let fixed = format_c!("{}_{}_0x{:x}_{}", first, second, third, fourth);
346 let heaped = format!("{}_{}_0x{:x}_{}", first, second, third, fourth);
347
348 assert!(!fixed.is_allocated());
349 assert_eq!(&fixed.to_string(), &heaped);
350 }
351
352 #[test]
353 fn test_long_fmt_macro() {
354 let first = "";
355 let second = gen_string(510);
356 let third = 3;
357 let fourth = gen_string(513 * 8);
358
359 let fixed = format_c!("{}_{}{}{}", first, second, third, fourth);
360 let heaped = format!("{}_{}{}{}", first, second, third, fourth);
361
362 assert!(fixed.is_allocated());
363 assert_eq!(&fixed.to_string(), &heaped);
364 }
365}