Skip to main content

static_collections/ffi/
c_str.rs

1// C-String
2
3use core::{cmp::Ordering, ffi::CStr, fmt, mem::MaybeUninit, ops::AddAssign, slice};
4
5/// This error is used to indicate the string is not null-terminated.
6#[derive(Debug)]
7pub struct NotNullTerminatedError;
8
9// Usually, CRT routines are seriously optimized by target vendor.
10// However, it depends on target vendor's implementation, and the target vendor may not even provide CRT at all.
11#[cfg(feature = "use_crt")]
12unsafe extern "C"
13{
14	fn strncat(dest:*mut i8,src:*const i8,cch:usize)->*mut i8;
15	fn strncmp(s1:*const i8,s2:*const i8,cch:usize)->isize;
16	fn strncpy(dest:*mut i8,src:*const i8,cch:usize)->*mut i8;
17	pub(crate) fn strnlen(str:*const i8,cch:usize)->usize;
18}
19
20// If usage of CRT is disabled, implement CRT on our own.
21
22#[cfg(not(feature = "use_crt"))]
23#[unsafe(no_mangle)] pub(crate) unsafe extern "C" fn strnlen(str:*const i8,cch:usize)->usize
24{
25	let s=unsafe{slice::from_raw_parts(str,cch)};
26	s.iter().position(|&v| v==0).unwrap_or(cch)
27}
28
29#[cfg(not(feature = "use_crt"))]
30#[unsafe(no_mangle)] unsafe extern "C" fn strncpy(dest:*mut i8,src:*const i8,cch:usize)->*mut i8
31{
32	let s1=unsafe{slice::from_raw_parts_mut(dest,cch)};
33	let s2=unsafe{slice::from_raw_parts(src,cch)};
34	for (i,&c) in s2.iter().enumerate()
35	{
36		s1[i]=c;
37		if c==0
38		{
39			break;
40		}
41	}
42	dest
43}
44
45#[cfg(not(feature = "use_crt"))]
46#[unsafe(no_mangle)] unsafe extern "C" fn strncmp(str1:*const i8,str2:*const i8,cch:usize)->isize
47{
48	let s1=unsafe{slice::from_raw_parts(str1,cch)};
49	let s2=unsafe{slice::from_raw_parts(str2,cch)};
50	for i in 0..cch
51	{
52		let diff=s1[i]-s2[i];
53		if diff!=0
54		{
55			return diff as isize
56		}
57	}
58	0
59}
60
61#[cfg(not(feature = "use_crt"))]
62#[unsafe(no_mangle)] unsafe extern "C" fn strncat(dest:*mut i8,src:*const i8,cch:usize)->*mut i8
63{
64	let start_index=unsafe{strnlen(dest,usize::MAX)};
65	let s1=unsafe{slice::from_raw_parts_mut(dest.add(start_index),cch)};
66	let s2=unsafe{slice::from_raw_parts(src,cch)};
67	for (i,&c) in s2.iter().enumerate()
68	{
69		s1[i]=c;
70		if c==0
71		{
72			break;
73		}
74	}
75	dest
76}
77
78/// A C-compatible, growable but fixed-capacity string. \
79/// The exact encoding of the string depends on the target platform. \
80/// The `StaticCString` guarantees a null-terminator at the end, so the maximum length is 1 less than capacity.
81#[derive(Clone)]
82pub struct StaticCString<const N:usize>
83{
84	buffer:MaybeUninit<[i8;N]>
85}
86
87impl<const N:usize> StaticCString<N>
88{
89	/// Creates a new empty `StaticCString`.
90	/// 
91	/// Given that the string is empty, the buffer that contains the string isn't initialized.
92	/// This means the initial operation is very inexpensive.
93	/// 
94	/// # Example
95	/// ```
96	/// use static_collections::ffi::c_str::StaticCString;
97	/// let s:StaticCString<32>=StaticCString::from(c"Hello, World!");
98	/// assert_eq!(s.len(),13);
99	/// ```
100	pub const fn new()->Self
101	{
102		let mut s=Self{buffer:MaybeUninit::uninit()};
103		unsafe
104		{
105			// Just ensure the first byte is null-character.
106			s.buffer.assume_init_mut()[0]=0;
107		}
108		s
109	}
110
111	/// Returns the length of the static-string by using `strnlen`.
112	/// 
113	/// # Example
114	/// ```
115	/// use static_collections::ffi::c_str::StaticCString;
116	/// let s:StaticCString<32>=StaticCString::from(c"Hello, World!");
117	/// assert_eq!(s.len(),13);
118	/// ```
119	#[inline(always)] pub fn len(&self)->usize
120	{
121		unsafe
122		{
123			strnlen(self.buffer.assume_init_ref().as_ptr(),N)
124		}
125	}
126
127	#[inline(always)] pub fn is_empty(&self)->bool
128	{
129		self.len()==0
130	}
131
132	/// Returns the capacity of the static-string.
133	/// 
134	/// # Example
135	/// ```
136	/// use static_collections::ffi::c_str::StaticCString;
137	/// let s:StaticCString<32>=StaticCString::from(c"Hello, World!");
138	/// assert_eq!(s.capacity(),32);
139	/// ```
140	#[inline(always)] pub const fn capacity(&self)->usize
141	{
142		N
143	}
144
145	/// Returns the immutable pointer to the first character.
146	/// 
147	/// The returned pointer can be passed to C-FFI routines that accepts raw pointers.
148	#[inline(always)] pub const fn as_ptr(&self)->*const i8
149	{
150		unsafe
151		{
152			self.buffer.assume_init_ref().as_ptr()
153		}
154	}
155
156	/// Returns the mutable pointer to the first character.
157	/// 
158	/// The returned pointer can be passed to C-FFI routines that accepts raw pointers.
159	#[inline(always)] pub const fn as_mut_ptr(&mut self)->*mut i8
160	{
161		unsafe
162		{
163			self.buffer.assume_init_mut().as_mut_ptr()
164		}
165	}
166
167	/// Returns the contents of this `StaticCString` as a slice of bytes.
168	/// 
169	/// The returned slice does **not** contain the trailing null-terminator, and it is
170	/// guaranteed to not contain any interior null bytes. If you need this null-terminator,
171	/// use `StaticCString::as_bytes_with_nul` instead.
172	#[inline(always)] pub fn as_bytes(&self)->&[u8]
173	{
174		let l=self.len();
175		unsafe
176		{
177			slice::from_raw_parts(self.as_ptr().cast(),l)
178		}
179	}
180
181	/// Returns the contents of this `StaticCString` as a mutable slice of bytes.
182	/// 
183	/// The returned slice does **not** contain the trailing null-terminator, and it is
184	/// guaranteed to not contain any interior null bytes. If you need this null-terminator,
185	/// use `StaticCString::as_mut_bytes_with_nul` instead.
186	#[inline(always)] pub fn as_mut_bytes(&mut self)->&mut [u8]
187	{
188		let l=self.len();
189		unsafe
190		{
191			slice::from_raw_parts_mut(self.as_mut_ptr().cast(),l)
192		}
193	}
194
195	/// Returns the contents of this `StaticCString` as a slice of bytes, including the trailing null-terminator.
196	#[inline(always)] pub fn as_bytes_with_nul(&self)->&[u8]
197	{
198		let l=self.len();
199		unsafe
200		{
201			slice::from_raw_parts(self.as_ptr().cast(),l+1)
202		}
203	}
204
205	/// Returns the contents of this `StaticCString` as a slice of bytes, including the trailing null-terminator.
206	/// 
207	/// # Safety
208	/// You must ensure the slice contains a null-terminator throughout the lifetime of the slice.
209	#[inline(always)] pub unsafe fn as_mut_bytes_with_nul(&mut self)->&mut [u8]
210	{
211		let l=self.len();
212		unsafe
213		{
214			slice::from_raw_parts_mut(self.as_mut_ptr().cast(),l+1)
215		}
216	}
217
218	#[inline(always)] pub fn as_c_str(&self)->&CStr
219	{
220		unsafe
221		{
222			CStr::from_ptr(self.as_ptr())
223		}
224	}
225}
226
227impl<'a,const N:usize> StaticCString<N>
228{
229	/// Gets an immutable fixed-capacity StaticCString object reference from a raw pointer.
230	/// 
231	/// Returns `Err(NotNullTerminatedError)` if the string is not null-terminated.
232	/// 
233	/// # Safety
234	/// You must ensure the lifetime of the `&'a StaticCString` lives long enough. \
235	/// You must ensure `ptr` points to a valid region that has `N` bytes.
236	/// 
237	/// # Panic
238	/// The `strnlen` will be called by this function. It may trigger an exception. \
239	/// The program may either panic, crash, or run normally if the exception is handled.
240	#[inline(always)] pub unsafe fn from_raw_ptr(ptr:*const i8)->Result<&'a Self,NotNullTerminatedError>
241	{
242		let r:&Self=unsafe{&*ptr.cast()};
243		if r.len()>=N
244		{
245			Err(NotNullTerminatedError)
246		}
247		else
248		{
249			Ok(r)
250		}
251	}
252	
253	/// Gets a mutable fixed-capacity StaticCString object reference from a raw pointer.
254	/// 
255	/// Returns `Err(NotNullTerminatedError)` if the string is not null-terminated.
256	/// 
257	/// # Safety
258	/// You must ensure the lifetime of the `&'a mut StaticCString` lives long enough. \
259	/// You must ensure `ptr` points to a valid region that has `N` bytes.
260	/// 
261	/// # Panic
262	/// The `strnlen` will be called by this function. It may trigger an exception. \
263	/// The program may either panic, crash, or run normally if the exception is handled.
264	#[inline(always)] pub unsafe fn from_raw_mut_ptr(ptr:*mut i8)->Result<&'a mut Self,NotNullTerminatedError>
265	{
266		let r:&mut Self=unsafe{&mut *ptr.cast()};
267		if r.len()>=N
268		{
269			Err(NotNullTerminatedError)
270		}
271		else
272		{
273			Ok(r)
274		}
275	}
276}
277
278impl<const N:usize> From<&CStr> for StaticCString<N>
279{
280	fn from(value: &CStr) -> Self
281	{
282		let mut s=Self::new();
283		unsafe
284		{
285			let p=value.as_ptr();
286			let q=s.buffer.assume_init_mut().as_mut_ptr();
287			strncpy(q,p,N);
288			// Force the final byte to null-character.
289			q.add(N-1).write(0);
290		}
291		s
292	}
293}
294
295impl<const M:usize,const N:usize> PartialEq<StaticCString<M>> for StaticCString<N>
296{
297	fn eq(&self, other: &StaticCString<M>) -> bool
298	{
299		unsafe
300		{
301			let p=self.buffer.assume_init_ref().as_ptr();
302			let q=other.buffer.assume_init_ref().as_ptr();
303			strncmp(p,q,if M<N {M} else {N})==0
304		}
305	}
306}
307
308impl<const M:usize,const N:usize> PartialOrd<StaticCString<M>> for StaticCString<N>
309{
310	fn partial_cmp(&self, other: &StaticCString<M>) -> Option<Ordering>
311	{
312		let r=unsafe
313		{
314			let p=self.buffer.assume_init_ref().as_ptr();
315			let q=other.buffer.assume_init_ref().as_ptr();
316			strncmp(p,q,if M<N {M} else {N})
317		};
318		match r
319		{
320			..0=>Some(Ordering::Less),
321			0=>Some(Ordering::Equal),
322			1.. =>Some(Ordering::Greater)
323		}
324	}
325}
326
327impl<const M:usize,const N:usize> AddAssign<StaticCString<M>> for StaticCString<N>
328{
329	fn add_assign(&mut self, rhs: StaticCString<M>)
330	{
331		unsafe
332		{
333			let p=rhs.buffer.assume_init_ref().as_ptr();
334			let q=self.buffer.assume_init_mut().as_mut_ptr();
335			strncat(q,p,if M<N {M} else {N});
336			// Force the final byte to null-character.
337			q.add(N-1).write(0);
338		}
339	}
340}
341
342// The CString type does not implement Display trait. So won't we.
343// Only Debug trait will be implemented.
344impl<const N:usize> fmt::Debug for StaticCString<N>
345{
346	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
347	{
348		self.as_c_str().fmt(f)
349	}
350}
351
352impl<const N:usize> Default for StaticCString<N>
353{
354	fn default() -> Self
355	{
356		Self::new()
357	}
358}
359
360unsafe impl<const N:usize> Send for StaticCString<N> {}
361unsafe impl<const N:usize> Sync for StaticCString<N> {}