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