Skip to main content

roboplc_io_ads/
strings.rs

1//! Const-generic string types for representing fixed-length strings.
2
3/// Represents a fixed-length byte string.
4///
5/// This type can be created from a `&str` or `&[u8]` if their byte
6/// length does not exceed the fixed length.
7///
8/// It can be freely converted from and to a `[u8; N]` array, and
9/// to a `Vec<u8>` where it will be cut at the first null byte.
10///
11/// It can be converted to a Rust `String` if it is UTF8 encoded.
12#[repr(C)]
13#[derive(Clone, Copy)]
14pub struct String<const LEN: usize>([u8; LEN], u8); // one extra NULL byte
15
16impl<const LEN: usize> String<LEN> {
17    /// Create a new empty string.
18    pub fn new() -> Self {
19        Self([0; LEN], 0)
20    }
21
22    /// Return the number of bytes up to the first null byte.
23    pub fn len(&self) -> usize {
24        self.0.iter().position(|&b| b == 0).unwrap_or(self.0.len())
25    }
26
27    pub fn is_empty(&self) -> bool {
28        self.0[0] == 0
29    }
30
31    /// Get the slice up to the first null byte.
32    pub fn as_bytes(&self) -> &[u8] {
33        &self.0[..self.len()]
34    }
35
36    /// Get a reference to the full array of bytes.
37    pub fn backing_array(&mut self) -> &mut [u8; LEN] {
38        &mut self.0
39    }
40}
41
42// standard traits
43
44impl<const LEN: usize> std::default::Default for String<LEN> {
45    fn default() -> Self {
46        Self::new()
47    }
48}
49
50impl<const LEN: usize> std::fmt::Debug for String<LEN> {
51    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
52        std::fmt::Debug::fmt(&std::string::String::from_utf8_lossy(self.as_bytes()), fmt)
53    }
54}
55
56impl<const LEN: usize> std::cmp::PartialEq for String<LEN> {
57    fn eq(&self, other: &Self) -> bool {
58        self.as_bytes() == other.as_bytes()
59    }
60}
61
62impl<const LEN: usize> std::cmp::PartialEq<&[u8]> for String<LEN> {
63    fn eq(&self, other: &&[u8]) -> bool {
64        self.as_bytes() == *other
65    }
66}
67
68impl<const LEN: usize> std::cmp::PartialEq<&str> for String<LEN> {
69    fn eq(&self, other: &&str) -> bool {
70        self.as_bytes() == other.as_bytes()
71    }
72}
73
74// conversion with [u8; N]
75
76impl<const LEN: usize> std::convert::From<[u8; LEN]> for String<LEN> {
77    fn from(arr: [u8; LEN]) -> Self {
78        Self(arr, 0)
79    }
80}
81
82impl<const LEN: usize> std::convert::From<String<LEN>> for [u8; LEN] {
83    fn from(bstr: String<LEN>) -> Self {
84        bstr.0
85    }
86}
87
88// conversion with bytes
89
90impl<const LEN: usize> std::convert::TryFrom<&'_ [u8]> for String<LEN> {
91    type Error = ();
92    fn try_from(arr: &[u8]) -> std::result::Result<Self, ()> {
93        if arr.len() > LEN {
94            return Err(());
95        }
96        let mut bstr = Self::new();
97        bstr.0[..arr.len()].copy_from_slice(arr);
98        Ok(bstr)
99    }
100}
101
102impl<const LEN: usize> std::convert::From<String<LEN>> for std::vec::Vec<u8> {
103    fn from(bstr: String<LEN>) -> Self {
104        bstr.as_bytes().to_vec()
105    }
106}
107
108// conversion with strings
109
110impl<const LEN: usize> std::convert::TryFrom<&'_ str> for String<LEN> {
111    type Error = ();
112    fn try_from(s: &str) -> std::result::Result<Self, ()> {
113        Self::try_from(s.as_bytes())
114    }
115}
116
117impl<const LEN: usize> std::convert::TryFrom<String<LEN>> for std::string::String {
118    type Error = std::str::Utf8Error;
119    fn try_from(bstr: String<LEN>) -> std::result::Result<Self, Self::Error> {
120        std::str::from_utf8(bstr.as_bytes()).map(Into::into)
121    }
122}
123
124// zerocopy implementations
125
126unsafe impl<const LEN: usize> zerocopy::AsBytes for String<LEN> {
127    fn only_derive_is_allowed_to_implement_this_trait() {}
128}
129
130unsafe impl<const LEN: usize> zerocopy::FromBytes for String<LEN> {
131    fn only_derive_is_allowed_to_implement_this_trait() {}
132}
133
134/// Represents a fixed-length wide string.
135///
136/// This type can be created from a `&[u16]` if its length does not
137/// exceed the fixed length.  It can be created from a `&str` if its
138/// length, encoded in UTF16, does not exceed the fixed length.
139///
140/// It can be freely converted from and to a `[u16; N]` array, and
141/// to a `Vec<u16>` where it will be cut at the first null.
142///
143/// It can be converted to a Rust `String` if it is properly UTF16
144/// encoded.
145#[repr(C)]
146#[derive(Clone, Copy)]
147pub struct WString<const LEN: usize>([u16; LEN], u16); // one extra NULL byte
148
149impl<const LEN: usize> WString<LEN> {
150    /// Create a new empty string.
151    pub fn new() -> Self {
152        Self([0; LEN], 0)
153    }
154
155    /// Return the number of code units up to the first null.
156    pub fn len(&self) -> usize {
157        self.0.iter().position(|&b| b == 0).unwrap_or(self.0.len())
158    }
159
160    pub fn is_empty(&self) -> bool {
161        self.0[0] == 0
162    }
163
164    /// Get the slice up to the first null code unit.
165    pub fn as_slice(&self) -> &[u16] {
166        &self.0[..self.len()]
167    }
168
169    /// Get a reference to the full array of code units.
170    pub fn backing_array(&mut self) -> &mut [u16; LEN] {
171        &mut self.0
172    }
173}
174
175impl<const LEN: usize> std::default::Default for WString<LEN> {
176    fn default() -> Self {
177        Self::new()
178    }
179}
180
181impl<const LEN: usize> std::fmt::Debug for WString<LEN> {
182    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
183        let fmted: std::string::String =
184            std::char::decode_utf16(self.0.iter().copied().take_while(|&b| b != 0))
185                .map(|ch| ch.unwrap_or(std::char::REPLACEMENT_CHARACTER))
186                .collect();
187        std::fmt::Debug::fmt(&fmted, fmt)
188    }
189}
190
191impl<const LEN: usize> std::cmp::PartialEq for WString<LEN> {
192    fn eq(&self, other: &Self) -> bool {
193        self.as_slice() == other.as_slice()
194    }
195}
196
197impl<const LEN: usize> std::cmp::PartialEq<&[u16]> for WString<LEN> {
198    fn eq(&self, other: &&[u16]) -> bool {
199        self.as_slice() == *other
200    }
201}
202
203impl<const LEN: usize> std::cmp::PartialEq<&str> for WString<LEN> {
204    fn eq(&self, other: &&str) -> bool {
205        self.as_slice().iter().copied().eq(other.encode_utf16())
206    }
207}
208
209// conversion with [u16; N]
210
211impl<const LEN: usize> std::convert::From<[u16; LEN]> for WString<LEN> {
212    fn from(arr: [u16; LEN]) -> Self {
213        Self(arr, 0)
214    }
215}
216
217impl<const LEN: usize> std::convert::From<WString<LEN>> for [u16; LEN] {
218    fn from(wstr: WString<LEN>) -> Self {
219        wstr.0
220    }
221}
222
223// conversion with [u16]
224
225impl<const LEN: usize> std::convert::TryFrom<&'_ [u16]> for WString<LEN> {
226    type Error = ();
227    fn try_from(arr: &[u16]) -> std::result::Result<Self, ()> {
228        if arr.len() > LEN {
229            return Err(());
230        }
231        let mut wstr = Self::new();
232        wstr.0[..arr.len()].copy_from_slice(arr);
233        Ok(wstr)
234    }
235}
236
237impl<const LEN: usize> std::convert::From<WString<LEN>> for std::vec::Vec<u16> {
238    fn from(wstr: WString<LEN>) -> Self {
239        wstr.as_slice().to_vec()
240    }
241}
242
243// conversion with strings
244
245impl<const LEN: usize> std::convert::TryFrom<&'_ str> for WString<LEN> {
246    type Error = ();
247    fn try_from(s: &str) -> std::result::Result<Self, ()> {
248        let mut wstr = Self::new();
249        for (i, unit) in s.encode_utf16().enumerate() {
250            if i >= wstr.0.len() {
251                return Err(());
252            }
253            wstr.0[i] = unit;
254        }
255        Ok(wstr)
256    }
257}
258
259impl<const LEN: usize> std::convert::TryFrom<WString<LEN>> for std::string::String {
260    type Error = std::char::DecodeUtf16Error;
261    fn try_from(wstr: WString<LEN>) -> std::result::Result<Self, Self::Error> {
262        std::char::decode_utf16(wstr.0.iter().copied().take_while(|&b| b != 0)).collect()
263    }
264}
265
266// zerocopy implementations
267
268unsafe impl<const LEN: usize> zerocopy::AsBytes for WString<LEN> {
269    fn only_derive_is_allowed_to_implement_this_trait() {}
270}
271
272unsafe impl<const LEN: usize> zerocopy::FromBytes for WString<LEN> {
273    fn only_derive_is_allowed_to_implement_this_trait() {}
274}
275
276// compatibility aliases
277
278/// Alias for `String<80>`.
279pub type String80 = String<80>;
280
281/// Alias for `WString<80>`.
282pub type WString80 = WString<80>;