1use crate::{Arena, Vec};
2use allocator_api2::alloc::Allocator;
3use std::fmt;
4use std::hash::{Hash, Hasher};
5use std::io;
6use std::ops::Deref;
7
8#[repr(C)]
19pub struct String<'a, A: Allocator = &'a Arena> {
20 bytes: Vec<'a, u8, A>,
21}
22
23impl<'a, A: Allocator> String<'a, A> {
24 #[inline]
26 pub fn new_in(alloc: A) -> Self {
27 Self { bytes: Vec::new_in(alloc) }
28 }
29
30 #[inline]
32 pub fn with_capacity_in(cap: usize, alloc: A) -> Self {
33 Self { bytes: Vec::with_capacity_in(cap, alloc) }
34 }
35
36 pub fn from_reader_in<R: io::Read>(mut reader: R, alloc: A) -> io::Result<Self> {
41 const CHUNK: usize = 8 * 1024;
43 let mut bytes = Vec::new_in(alloc);
44 let mut filled = 0;
45 loop {
46 if filled == bytes.len() {
47 bytes.extend_from_slice(&[0; CHUNK]);
48 }
49 match reader.read(&mut bytes[filled..]) {
50 Ok(0) => break,
51 Ok(read) => filled += read,
52 Err(err) if err.kind() == io::ErrorKind::Interrupted => continue,
53 Err(err) => return Err(err),
54 }
55 }
56 bytes.truncate(filled);
57 match std::str::from_utf8(&bytes) {
58 Ok(_) => Ok(Self { bytes }),
59 Err(err) => Err(io::Error::new(io::ErrorKind::InvalidData, err)),
60 }
61 }
62
63 #[inline]
65 pub fn len(&self) -> usize {
66 self.bytes.len()
67 }
68
69 #[inline]
70 pub fn is_empty(&self) -> bool {
71 self.bytes.is_empty()
72 }
73
74 #[inline]
76 pub fn capacity(&self) -> usize {
77 self.bytes.capacity()
78 }
79
80 #[inline]
82 pub fn push_str(&mut self, str: &str) {
83 self.bytes.extend_from_slice(str.as_bytes());
84 }
85
86 #[inline]
88 pub fn push(&mut self, char: char) {
89 let mut buf = [0; 4];
90 self.push_str(char.encode_utf8(&mut buf));
91 }
92
93 #[inline]
95 pub fn as_str(&self) -> &str {
96 Self::as_utf8(&self.bytes)
97 }
98
99 #[inline]
104 pub fn into_str(self) -> &'a str {
105 Self::as_utf8(self.bytes.into_slice())
106 }
107
108 #[inline]
109 fn as_utf8(bytes: &[u8]) -> &str {
110 debug_assert!(std::str::from_utf8(bytes).is_ok(), "arena String must always hold valid UTF-8");
111 unsafe { std::str::from_utf8_unchecked(bytes) }
115 }
116}
117
118impl<'a, A: Allocator> Deref for String<'a, A> {
119 type Target = str;
120
121 #[inline]
122 fn deref(&self) -> &str {
123 self.as_str()
124 }
125}
126
127impl<'a, A: Allocator> AsRef<str> for String<'a, A> {
128 #[inline]
129 fn as_ref(&self) -> &str {
130 self.as_str()
131 }
132}
133
134impl<'a, A: Allocator> fmt::Write for String<'a, A> {
135 #[inline]
136 fn write_str(&mut self, str: &str) -> fmt::Result {
137 self.push_str(str);
138 Ok(())
139 }
140}
141
142impl<'a, A: Allocator> fmt::Display for String<'a, A> {
143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144 fmt::Display::fmt(self.as_str(), f)
145 }
146}
147
148impl<'a, A: Allocator> fmt::Debug for String<'a, A> {
149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150 fmt::Debug::fmt(self.as_str(), f)
151 }
152}
153
154impl<'a, A: Allocator> PartialEq for String<'a, A> {
155 fn eq(&self, other: &Self) -> bool {
156 **self == **other
157 }
158}
159
160impl<'a, A: Allocator> Eq for String<'a, A> {}
161
162impl<'a, A: Allocator> PartialEq<str> for String<'a, A> {
163 fn eq(&self, other: &str) -> bool {
164 &**self == other
165 }
166}
167
168impl<'a, A: Allocator> PartialEq<&str> for String<'a, A> {
169 fn eq(&self, other: &&str) -> bool {
170 &**self == *other
171 }
172}
173
174impl<'a, A: Allocator> Hash for String<'a, A> {
175 fn hash<H: Hasher>(&self, state: &mut H) {
176 (**self).hash(state);
177 }
178}
179
180#[macro_export]
189macro_rules! format_in {
190 (in $alloc:expr, $($arg:tt)*) => {{
191 let mut str = $crate::String::new_in($alloc);
192 ::core::fmt::Write::write_fmt(&mut str, ::core::format_args!($($arg)*))
193 .expect("formatting into an arena String cannot fail");
194 str
195 }};
196}
197
198#[cfg(test)]
199mod test {
200 use super::String;
201 use crate::Arena;
202 use std::fmt::Write;
203 use std::io::{self, Read};
204
205 struct DribbleReader<'r> {
207 bytes: &'r [u8],
208 interrupt_at: usize,
209 reads: usize,
210 }
211
212 impl<'r> Read for DribbleReader<'r> {
213 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
214 self.reads += 1;
215 if self.reads == self.interrupt_at {
216 return Err(io::Error::from(io::ErrorKind::Interrupted));
217 }
218 let Some((first, rest)) = self.bytes.split_first() else {
219 return Ok(0);
220 };
221 buf[0] = *first;
222 self.bytes = rest;
223 Ok(1)
224 }
225 }
226
227 #[test]
228 fn new_is_empty_and_allocates_nothing() {
229 let alloc = Arena::default();
230 let str = String::new_in(&alloc);
231 assert!(str.is_empty());
232 assert_eq!(str.len(), 0);
233 assert_eq!(str.as_str(), "");
234 }
235
236 #[test]
237 fn push_str_appends_in_order() {
238 let alloc = Arena::default();
239 let mut str = String::new_in(&alloc);
240 str.push_str("foo");
241 str.push_str("bar");
242 assert_eq!(str.as_str(), "foobar");
243 assert_eq!(str.len(), 6);
244 }
245
246 #[test]
247 fn push_encodes_multibyte_chars() {
248 let alloc = Arena::default();
249 let mut str = String::new_in(&alloc);
250 str.push('a');
251 str.push('£');
252 str.push('😀');
253 assert_eq!(str.as_str(), "a£😀");
254 assert_eq!(str.len(), 7);
256 }
257
258 #[test]
259 fn write_macro_formats_through_fmt_write() {
260 let alloc = Arena::default();
261 let mut str = String::new_in(&alloc);
262 write!(&mut str, "{}px", 12).unwrap();
263 assert_eq!(str.as_str(), "12px");
264 }
265
266 #[test]
267 fn format_in_builds_a_string() {
268 let alloc = Arena::default();
269 let str = format_in!(in &alloc, "{}{}", 12, "px");
270 assert_eq!(str, "12px");
271 }
272
273 #[test]
274 fn into_str_outlives_the_string() {
275 let alloc = Arena::default();
276 let borrowed: &str = {
277 let mut str = String::new_in(&alloc);
278 str.push_str("outlives");
279 str.into_str()
280 };
281 assert_eq!(borrowed, "outlives");
282 }
283
284 #[test]
285 fn survives_reallocation() {
286 let alloc = Arena::default();
287 let mut str = String::new_in(&alloc);
288 for i in 0..512 {
289 write!(&mut str, "{}", i % 10).unwrap();
290 }
291 let expected: std::string::String = (0..512).map(|i| char::from(b'0' + (i % 10) as u8)).collect();
293 assert_eq!(str.len(), 512);
294 assert_eq!(str.as_str(), expected);
295 }
296
297 #[test]
298 fn with_capacity_reserves_without_writing() {
299 let alloc = Arena::default();
300 let mut str = String::with_capacity_in(16, &alloc);
301 assert!(str.is_empty());
302 assert!(str.capacity() >= 16);
303 str.push_str("fits");
304 assert_eq!(str.as_str(), "fits");
305 }
306
307 #[test]
308 fn deref_exposes_str_methods() {
309 let alloc = Arena::default();
310 let mut str = String::new_in(&alloc);
311 str.push_str(" padded ");
312 assert_eq!(str.trim(), "padded");
313 assert!(str.contains("padded"));
314 }
315
316 #[test]
317 fn equality_against_str_and_self() {
318 let alloc = Arena::default();
319 let mut a = String::new_in(&alloc);
320 a.push_str("same");
321 let mut b = String::new_in(&alloc);
322 b.push_str("same");
323 assert_eq!(a, b);
324 assert_eq!(a, "same");
325 assert_eq!(a, *"same");
326 }
327
328 #[test]
329 fn from_reader_in_reads_to_end() {
330 let alloc = Arena::default();
331 let str = String::from_reader_in("body{color:blue}".as_bytes(), &alloc).unwrap();
332 assert_eq!(str.as_str(), "body{color:blue}");
333 assert_eq!(str.len(), 16);
334 }
335
336 #[test]
337 fn from_reader_in_rejects_invalid_utf8() {
338 let alloc = Arena::default();
339 let err = String::from_reader_in(&[b'a', 0xff, b'b'][..], &alloc).unwrap_err();
340 assert_eq!(err.kind(), io::ErrorKind::InvalidData);
341 }
342
343 #[test]
344 fn from_reader_in_grows_past_one_chunk() {
345 let alloc = Arena::default();
346 let mut expected = "x".repeat(8 * 1024 - 2);
348 expected.push('😀');
349 expected.push_str(&"y".repeat(4096));
350 let str = String::from_reader_in(expected.as_bytes(), &alloc).unwrap();
351 assert_eq!(str.len(), expected.len());
352 assert_eq!(str.as_str(), expected);
353 }
354
355 #[test]
356 fn from_reader_in_handles_partial_reads_and_interruptions() {
357 let alloc = Arena::default();
358 let reader = DribbleReader { bytes: "a£😀b".as_bytes(), interrupt_at: 3, reads: 0 };
359 let str = String::from_reader_in(reader, &alloc).unwrap();
360 assert_eq!(str.as_str(), "a£😀b");
361 }
362}