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.
10unsafe extern "C"
11{
12	fn strncat(dest:*mut i8,src:*const i8,cch:usize)->*mut i8;
13	fn strncmp(s1:*const i8,s2:*const i8,cch:usize)->isize;
14	fn strncpy(dest:*mut i8,src:*const i8,cch:usize)->*mut i8;
15	pub(crate) fn strnlen(str:*const i8,cch:usize)->usize;
16}
17
18/// A C-compatible, growable but fixed-capacity string. \
19/// The exact encoding of the string depends on the target platform. \
20/// The `StaticCString` guarantees a null-terminator at the end, so the maximum length is 1 less than capacity.
21/// # Examples
22pub struct StaticCString<const N:usize>
23{
24	buffer:MaybeUninit<[i8;N]>
25}
26
27impl<const N:usize> StaticCString<N>
28{
29	/// Creates a new empty `StaticCString`.
30	/// 
31	/// Given that the string is empty, the buffer that contains the string isn't initialized.
32	/// This means the initial operation is very inexpensive.
33	/// 
34	/// # Example
35	/// ```
36	/// use static_collections::ffi::c_str::StaticCString;
37	/// let s:StaticCString<32>=StaticCString::from(c"Hello, World!");
38	/// assert_eq!(s.len(),13);
39	/// ```
40	pub const fn new()->Self
41	{
42		let mut s=Self{buffer:MaybeUninit::uninit()};
43		unsafe
44		{
45			// Just ensure the first byte is null-character.
46			s.buffer.assume_init_mut()[0]=0;
47		}
48		s
49	}
50
51	/// Returns the length of the static-string by using `strnlen`.
52	/// 
53	/// # Example
54	/// ```
55	/// use static_collections::ffi::c_str::StaticCString;
56	/// let s:StaticCString<32>=StaticCString::from(c"Hello, World!");
57	/// assert_eq!(s.len(),13);
58	/// ```
59	#[inline(always)] pub fn len(&self)->usize
60	{
61		unsafe
62		{
63			strnlen(self.buffer.assume_init_ref().as_ptr(),N)
64		}
65	}
66
67	#[inline(always)] pub fn is_empty(&self)->bool
68	{
69		self.len()==0
70	}
71
72	/// Returns the capacity of the static-string.
73	/// 
74	/// # Example
75	/// ```
76	/// use static_collections::ffi::c_str::StaticCString;
77	/// let s:StaticCString<32>=StaticCString::from(c"Hello, World!");
78	/// assert_eq!(s.capacity(),32);
79	/// ```
80	#[inline(always)] pub const fn capacity(&self)->usize
81	{
82		N
83	}
84
85	/// Returns the immutable pointer to the first character.
86	/// 
87	/// The returned pointer can be passed to C-FFI routines that accepts raw pointers.
88	#[inline(always)] pub const fn as_ptr(&self)->*const i8
89	{
90		unsafe
91		{
92			self.buffer.assume_init_ref().as_ptr()
93		}
94	}
95
96	/// Returns the mutable pointer to the first character.
97	/// 
98	/// The returned pointer can be passed to C-FFI routines that accepts raw pointers.
99	#[inline(always)] pub const fn as_mut_ptr(&mut self)->*mut i8
100	{
101		unsafe
102		{
103			self.buffer.assume_init_mut().as_mut_ptr()
104		}
105	}
106
107	/// Returns the contents of this `StaticCString` as a slice of bytes.
108	/// 
109	/// The returned slice does **not** contain the trailing null-terminator, and it is
110	/// guaranteed to not contain any interior null bytes. If you need this null-terminator,
111	/// use `StaticCString::as_bytes_with_nul` instead.
112	#[inline(always)] pub fn as_bytes(&self)->&[u8]
113	{
114		let l=self.len();
115		unsafe
116		{
117			slice::from_raw_parts(self.as_ptr().cast(),l)
118		}
119	}
120
121	/// Returns the contents of this `StaticCString` as a mutable slice of bytes.
122	/// 
123	/// The returned slice does **not** contain the trailing null-terminator, and it is
124	/// guaranteed to not contain any interior null bytes. If you need this null-terminator,
125	/// use `StaticCString::as_mut_bytes_with_nul` instead.
126	#[inline(always)] pub fn as_mut_bytes(&mut self)->&mut [u8]
127	{
128		let l=self.len();
129		unsafe
130		{
131			slice::from_raw_parts_mut(self.as_mut_ptr().cast(),l)
132		}
133	}
134
135	/// Returns the contents of this `StaticCString` as a slice of bytes, including the trailing null-terminator.
136	#[inline(always)] pub fn as_bytes_with_nul(&self)->&[u8]
137	{
138		let l=self.len();
139		unsafe
140		{
141			slice::from_raw_parts(self.as_ptr().cast(),l+1)
142		}
143	}
144
145	/// Returns the contents of this `StaticCString` as a slice of bytes, including the trailing null-terminator.
146	/// 
147	/// # Safety
148	/// You must ensure the slice contains a null-terminator throughout the lifetime of the slice.
149	#[inline(always)] pub unsafe fn as_mut_bytes_with_nul(&mut self)->&mut [u8]
150	{
151		let l=self.len();
152		unsafe
153		{
154			slice::from_raw_parts_mut(self.as_mut_ptr().cast(),l+1)
155		}
156	}
157
158	#[inline(always)] pub fn as_c_str(&self)->&CStr
159	{
160		unsafe
161		{
162			CStr::from_ptr(self.as_ptr())
163		}
164	}
165}
166
167impl<'a,const N:usize> StaticCString<N>
168{
169	#[inline(always)] pub fn from_raw_ptr(ptr:*const i8)->Result<&'a Self,NotNullTerminatedError>
170	{
171		let r:&Self=unsafe{&*ptr.cast()};
172		if r.len()>=N
173		{
174			Err(NotNullTerminatedError)
175		}
176		else
177		{
178			Ok(r)
179		}
180	}
181	
182	#[inline(always)] pub fn from_raw_mut_ptr(ptr:*mut i8)->Result<&'a mut Self,NotNullTerminatedError>
183	{
184		let r:&mut Self=unsafe{&mut *ptr.cast()};
185		if r.len()>=N
186		{
187			Err(NotNullTerminatedError)
188		}
189		else
190		{
191			Ok(r)
192		}
193	}
194}
195
196impl<const N:usize> From<&CStr> for StaticCString<N>
197{
198	fn from(value: &CStr) -> Self
199	{
200		let mut s=Self::new();
201		unsafe
202		{
203			let p=value.as_ptr();
204			let q=s.buffer.assume_init_mut().as_mut_ptr();
205			strncpy(q,p,N);
206			// Force the final byte to null-character.
207			q.add(N-1).write(0);
208		}
209		s
210	}
211}
212
213impl<const M:usize,const N:usize> PartialEq<StaticCString<M>> for StaticCString<N>
214{
215	fn eq(&self, other: &StaticCString<M>) -> bool
216	{
217		unsafe
218		{
219			let p=self.buffer.assume_init_ref().as_ptr();
220			let q=other.buffer.assume_init_ref().as_ptr();
221			strncmp(p,q,if M<N {M} else {N})==0
222		}
223	}
224}
225
226impl<const M:usize,const N:usize> PartialOrd<StaticCString<M>> for StaticCString<N>
227{
228	fn partial_cmp(&self, other: &StaticCString<M>) -> Option<Ordering>
229	{
230		let r=unsafe
231		{
232			let p=self.buffer.assume_init_ref().as_ptr();
233			let q=other.buffer.assume_init_ref().as_ptr();
234			strncmp(p,q,if M<N {M} else {N})
235		};
236		match r
237		{
238			..0=>Some(Ordering::Less),
239			0=>Some(Ordering::Equal),
240			1.. =>Some(Ordering::Greater)
241		}
242	}
243}
244
245impl<const M:usize,const N:usize> AddAssign<StaticCString<M>> for StaticCString<N>
246{
247	fn add_assign(&mut self, rhs: StaticCString<M>)
248	{
249		unsafe
250		{
251			let p=rhs.buffer.assume_init_ref().as_ptr();
252			let q=self.buffer.assume_init_mut().as_mut_ptr();
253			strncat(q,p,if M<N {M} else {N});
254			// Force the final byte to null-character.
255			q.add(N-1).write(0);
256		}
257	}
258}
259
260// The CString type does not implement Display trait. So won't we.
261// Only Debug trait will be implemented.
262impl<const N:usize> fmt::Debug for StaticCString<N>
263{
264	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
265	{
266		self.as_c_str().fmt(f)
267	}
268}
269
270impl<const N:usize> Default for StaticCString<N>
271{
272	fn default() -> Self
273	{
274		Self::new()
275	}
276}
277
278unsafe impl<const N:usize> Send for StaticCString<N> {}
279unsafe impl<const N:usize> Sync for StaticCString<N> {}