1use rustc_hash::{FxBuildHasher, FxHashMap, FxHasher};
2use std::hash::{Hash, Hasher};
3use std::str;
4
5pub type StringHandle = u32;
6
7#[derive(Debug, Clone, Default)]
8pub struct StringInterner {
9 map: FxHashMap<u64, StringHandle>,
10 storage: Vec<u8>,
11 offsets: Vec<(u32, u32)>,
12}
13
14impl StringInterner {
15 pub fn new() -> Self {
16 Self::with_capacity(1000, 100000)
17 }
18
19 pub fn with_capacity(capacity: usize, bytes: usize) -> Self {
20 Self {
21 map: FxHashMap::with_capacity_and_hasher(capacity, FxBuildHasher),
22 storage: Vec::with_capacity(bytes),
23 offsets: Vec::with_capacity(capacity),
24 }
25 }
26
27 pub fn intern(&mut self, s: &str) -> StringHandle {
28 let mut hasher = FxHasher::default();
29 s.hash(&mut hasher);
30 let mut hash = hasher.finish();
31
32 loop {
33 match self.map.get(&hash) {
34 None => {
35 let storage_index = self.storage.len() as u32;
36 let offset_index = self.offsets.len() as u32;
37 self.map.insert(hash, offset_index);
38 self.storage.extend_from_slice(s.as_bytes());
39 self.offsets.push((storage_index, s.len() as u32));
40 return offset_index;
41 }
42 Some(&index) => {
43 if self.get(index) == s {
44 return index;
45 }
46 hash = hash.wrapping_add(1);
47 }
48 }
49 }
50 }
51
52 pub fn get(&self, handle: StringHandle) -> &str {
53 let offset = &self.offsets[handle as usize];
54 let begin = offset.0 as usize;
55 let end = begin + offset.1 as usize;
56
57 unsafe { str::from_utf8_unchecked(&self.storage[begin..end]) }
58 }
59
60 pub fn concat(&mut self, handle1: StringHandle, handle2: StringHandle) -> StringHandle {
61 let (str1_idx, str1_length) = self.offsets[handle1 as usize];
62 let (str2_idx, str2_length) = self.offsets[handle2 as usize];
63 let capacity = str1_length + str2_length;
64 let str1_begin = str1_idx as usize;
65 let str1_end = str1_begin + str1_length as usize;
66 let str2_begin = str2_idx as usize;
67 let str2_end = str2_begin + str2_length as usize;
68 let mut concat_string_bytes = Vec::with_capacity(capacity as usize);
69 concat_string_bytes.extend_from_slice(&self.storage[str1_begin..str1_end]);
70 concat_string_bytes.extend_from_slice(&self.storage[str2_begin..str2_end]);
71
72 let concat_string = unsafe { str::from_utf8_unchecked(&concat_string_bytes) };
73
74 self.intern(concat_string)
75 }
76
77 pub fn starts_with(
78 &self,
79 string_handle: StringHandle,
80 starts_with_handle: StringHandle,
81 ) -> bool {
82 let (str_idx, str_len) = self.offsets[string_handle as usize];
83 let (prefix_idx, prefix_len) = self.offsets[starts_with_handle as usize];
84
85 if prefix_len > str_len {
86 return false;
87 }
88
89 let str_begin = str_idx as usize;
90 let prefix_begin = prefix_idx as usize;
91 let prefix_end = prefix_begin + prefix_len as usize;
92
93 &self.storage[str_begin..str_begin + prefix_len as usize]
94 == &self.storage[prefix_begin..prefix_end]
95 }
96
97 pub fn ends_with(&self, string_handle: StringHandle, suffix_handle: StringHandle) -> bool {
98 let (str_idx, str_len) = self.offsets[string_handle as usize];
99 let (suffix_idx, suffix_len) = self.offsets[suffix_handle as usize];
100
101 if suffix_len > str_len {
102 return false;
103 }
104
105 let str_end = str_idx as usize + str_len as usize;
106 let str_suffix_start = str_end - suffix_len as usize;
107 let suffix_begin = suffix_idx as usize;
108 let suffix_end = suffix_begin + suffix_len as usize;
109
110 &self.storage[str_suffix_start..str_end] == &self.storage[suffix_begin..suffix_end]
111 }
112
113 pub fn contains(&self, string_handle: StringHandle, needle_handle: StringHandle) -> bool {
114 let (str_idx, str_len) = self.offsets[string_handle as usize];
115 let (needle_idx, needle_len) = self.offsets[needle_handle as usize];
116
117 if needle_len > str_len {
118 return false;
119 }
120
121 if needle_len == 0 {
122 return true;
123 }
124
125 let str_begin = str_idx as usize;
126 let str_end = str_begin + str_len as usize;
127 let needle_begin = needle_idx as usize;
128 let needle_end = needle_begin + needle_len as usize;
129
130 let haystack = &self.storage[str_begin..str_end];
131 let needle = &self.storage[needle_begin..needle_end];
132
133 if needle_len as usize > haystack.len() {
135 return false;
136 }
137
138 for i in 0..=(haystack.len() - needle.len()) {
139 if &haystack[i..i + needle.len()] == needle {
140 return true;
141 }
142 }
143
144 false
145 }
146
147 pub fn to_uppercase(&mut self, handle: StringHandle) -> StringHandle {
148 let s = self.get(handle);
149 let uppercase = s.to_uppercase();
150 self.intern(&uppercase)
151 }
152
153 pub fn to_lowercase(&mut self, handle: StringHandle) -> StringHandle {
154 let s = self.get(handle);
155 let lowercase = s.to_lowercase();
156 self.intern(&lowercase)
157 }
158
159 pub fn length(&self, handle: StringHandle) -> usize {
160 self.get(handle).chars().count()
161 }
162
163 pub fn chars(&self, handle: StringHandle) -> impl Iterator<Item = char> + '_ {
164 self.get(handle).chars()
165 }
166
167 pub fn char_at(&mut self, handle: StringHandle, index: usize) -> Option<StringHandle> {
168 let char_opt = self.get(handle).chars().nth(index);
169 char_opt.map(|c| {
170 let mut buf = [0u8; 4];
171 let s = c.encode_utf8(&mut buf);
172 self.intern(s)
173 })
174 }
175
176 pub fn pad_start(
177 &mut self,
178 handle: StringHandle,
179 width: usize,
180 pad_char: char,
181 ) -> StringHandle {
182 let s = self.get(handle);
183 let current_len = s.chars().count();
184
185 if current_len >= width {
186 return handle;
187 }
188
189 let pad_count = width - current_len;
190 let mut padded = String::with_capacity(pad_count * pad_char.len_utf8() + s.len());
191
192 for _ in 0..pad_count {
193 padded.push(pad_char);
194 }
195 padded.push_str(s);
196
197 self.intern(&padded)
198 }
199
200 pub fn pad_end(&mut self, handle: StringHandle, width: usize, pad_char: char) -> StringHandle {
201 let s = self.get(handle);
202 let current_len = s.chars().count();
203
204 if current_len >= width {
205 return handle;
206 }
207
208 let pad_count = width - current_len;
209 let mut padded = String::with_capacity(s.len() + pad_count * pad_char.len_utf8());
210 padded.push_str(s);
211
212 for _ in 0..pad_count {
213 padded.push(pad_char);
214 }
215
216 self.intern(&padded)
217 }
218
219 pub fn trim(&mut self, handle: StringHandle) -> StringHandle {
220 let s = self.get(handle);
221 let trimmed = s.trim().to_string();
222 self.intern(&trimmed)
223 }
224
225 pub fn trim_start(&mut self, handle: StringHandle) -> StringHandle {
226 let s = self.get(handle);
227 let trimmed = s.trim_start().to_string();
228 self.intern(&trimmed)
229 }
230
231 pub fn trim_end(&mut self, handle: StringHandle) -> StringHandle {
232 let s = self.get(handle);
233 let trimmed = s.trim_end().to_string();
234 self.intern(&trimmed)
235 }
236
237 pub fn get_allocated_bytes(&self) -> usize {
238 self.storage.len() + self.offsets.len() * std::mem::size_of::<(u32, u32)>()
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245
246 #[test]
247 fn test_basic_interning() {
248 let mut interner = StringInterner::new();
249
250 let handle1 = interner.intern("hello");
251 let handle2 = interner.intern("world");
252 let handle3 = interner.intern("hello");
253
254 assert_eq!(handle1, handle3);
255 assert_ne!(handle1, handle2);
256 assert_eq!(interner.get(handle1), "hello");
257 assert_eq!(interner.get(handle2), "world");
258 }
259
260 #[test]
261 fn test_empty_string() {
262 let mut interner = StringInterner::new();
263
264 let handle = interner.intern("");
265 assert_eq!(interner.get(handle), "");
266 }
267
268 #[test]
269 fn test_unicode_strings() {
270 let mut interner = StringInterner::new();
271
272 let handle1 = interner.intern("π¦");
273 let handle2 = interner.intern("Hello, δΈη");
274
275 assert_eq!(interner.get(handle1), "π¦");
276 assert_eq!(interner.get(handle2), "Hello, δΈη");
277 }
278
279 #[test]
280 fn test_chars_iterator() {
281 let mut interner = StringInterner::new();
282
283 let handle = interner.intern("abc");
284 let chars: Vec<char> = interner.chars(handle).collect();
285
286 assert_eq!(chars, vec!['a', 'b', 'c']);
287 }
288
289 #[test]
290 fn test_hash_collisions() {
291 let mut interner = StringInterner::new();
292
293 let mut handles = Vec::new();
294 for i in 0..1000 {
295 let s = format!("string_{}", i);
296 let handle = interner.intern(&s);
297 handles.push((handle, s));
298 }
299
300 for (handle, expected) in handles {
301 assert_eq!(interner.get(handle), expected);
302 }
303 }
304
305 #[test]
306 fn test_deduplication() {
307 let mut interner = StringInterner::new();
308
309 let handle1 = interner.intern("test");
310 let handle2 = interner.intern("test");
311 let handle3 = interner.intern("test");
312
313 assert_eq!(handle1, handle2);
314 assert_eq!(handle2, handle3);
315
316 assert_eq!(interner.offsets.len(), 1);
317 }
318
319 #[test]
320 fn test_starts_with() {
321 let mut interner = StringInterner::new();
322
323 let hello = interner.intern("hello world");
324 let prefix1 = interner.intern("hello");
325 let prefix2 = interner.intern("world");
326 let prefix3 = interner.intern("");
327 let prefix4 = interner.intern("hello world");
328 let prefix5 = interner.intern("hello world!");
329
330 assert!(interner.starts_with(hello, prefix1));
331 assert!(!interner.starts_with(hello, prefix2));
332 assert!(interner.starts_with(hello, prefix3)); assert!(interner.starts_with(hello, prefix4)); assert!(!interner.starts_with(hello, prefix5)); }
336
337 #[test]
338 fn test_starts_with_unicode() {
339 let mut interner = StringInterner::new();
340
341 let text = interner.intern("π¦ Rust");
342 let prefix1 = interner.intern("π¦");
343 let prefix2 = interner.intern("Rust");
344
345 assert!(interner.starts_with(text, prefix1));
346 assert!(!interner.starts_with(text, prefix2));
347 }
348
349 #[test]
350 fn test_ends_with() {
351 let mut interner = StringInterner::new();
352
353 let hello = interner.intern("hello world");
354 let suffix1 = interner.intern("world");
355 let suffix2 = interner.intern("hello");
356 let suffix3 = interner.intern("");
357 let suffix4 = interner.intern("hello world");
358 let suffix5 = interner.intern("!hello world");
359
360 assert!(interner.ends_with(hello, suffix1));
361 assert!(!interner.ends_with(hello, suffix2));
362 assert!(interner.ends_with(hello, suffix3)); assert!(interner.ends_with(hello, suffix4)); assert!(!interner.ends_with(hello, suffix5)); }
366
367 #[test]
368 fn test_ends_with_unicode() {
369 let mut interner = StringInterner::new();
370
371 let text = interner.intern("Rust π¦");
372 let suffix1 = interner.intern("π¦");
373 let suffix2 = interner.intern("Rust");
374
375 assert!(interner.ends_with(text, suffix1));
376 assert!(!interner.ends_with(text, suffix2));
377 }
378
379 #[test]
380 fn test_contains() {
381 let mut interner = StringInterner::new();
382
383 let text = interner.intern("hello world");
384 let needle1 = interner.intern("lo wo");
385 let needle2 = interner.intern("hello");
386 let needle3 = interner.intern("world");
387 let needle4 = interner.intern("xyz");
388 let needle5 = interner.intern("");
389 let needle6 = interner.intern("hello world");
390 let needle7 = interner.intern("hello world!");
391
392 assert!(interner.contains(text, needle1));
393 assert!(interner.contains(text, needle2));
394 assert!(interner.contains(text, needle3));
395 assert!(!interner.contains(text, needle4));
396 assert!(interner.contains(text, needle5)); assert!(interner.contains(text, needle6)); assert!(!interner.contains(text, needle7)); }
400
401 #[test]
402 fn test_contains_unicode() {
403 let mut interner = StringInterner::new();
404
405 let text = interner.intern("Hello, δΈη!");
406 let needle1 = interner.intern("δΈη");
407 let needle2 = interner.intern("δΈ");
408 let needle3 = interner.intern("η");
409 let needle4 = interner.intern("π¦");
410
411 assert!(interner.contains(text, needle1));
412 assert!(interner.contains(text, needle2));
413 assert!(interner.contains(text, needle3));
414 assert!(!interner.contains(text, needle4));
415 }
416
417 #[test]
418 fn test_to_uppercase() {
419 let mut interner = StringInterner::new();
420
421 let hello = interner.intern("hello");
422 let upper = interner.to_uppercase(hello);
423
424 assert_eq!(interner.get(upper), "HELLO");
425 assert_eq!(interner.get(hello), "hello"); }
427
428 #[test]
429 fn test_to_uppercase_unicode() {
430 let mut interner = StringInterner::new();
431
432 let text = interner.intern("straΓe");
433 let upper = interner.to_uppercase(text);
434
435 assert_eq!(interner.get(upper), "STRASSE");
436 }
437
438 #[test]
439 fn test_to_uppercase_already_upper() {
440 let mut interner = StringInterner::new();
441
442 let upper = interner.intern("HELLO");
443 let result = interner.to_uppercase(upper);
444
445 assert_eq!(result, upper);
447 assert_eq!(interner.get(result), "HELLO");
448 }
449
450 #[test]
451 fn test_to_lowercase() {
452 let mut interner = StringInterner::new();
453
454 let hello = interner.intern("HELLO");
455 let lower = interner.to_lowercase(hello);
456
457 assert_eq!(interner.get(lower), "hello");
458 assert_eq!(interner.get(hello), "HELLO"); }
460
461 #[test]
462 fn test_to_lowercase_unicode() {
463 let mut interner = StringInterner::new();
464
465 let text = interner.intern("ΞΞΞΞ");
466 let lower = interner.to_lowercase(text);
467
468 assert_eq!(interner.get(lower), "Ξ±Ξ²Ξ³Ξ΄");
469 }
470
471 #[test]
472 fn test_to_lowercase_already_lower() {
473 let mut interner = StringInterner::new();
474
475 let lower = interner.intern("hello");
476 let result = interner.to_lowercase(lower);
477
478 assert_eq!(result, lower);
480 assert_eq!(interner.get(result), "hello");
481 }
482
483 #[test]
484 fn test_length() {
485 let mut interner = StringInterner::new();
486
487 let empty = interner.intern("");
488 let hello = interner.intern("hello");
489 let emoji = interner.intern("π¦");
490 let multi = interner.intern("Hello, δΈη");
491
492 assert_eq!(interner.length(empty), 0);
493 assert_eq!(interner.length(hello), 5);
494 assert_eq!(interner.length(emoji), 1); assert_eq!(interner.length(multi), 9); }
497
498 #[test]
499 fn test_length_multi_byte_chars() {
500 let mut interner = StringInterner::new();
501
502 let text = interner.intern("π¦π¦π¦");
503 assert_eq!(interner.length(text), 3);
504
505 let text2 = interner.intern("δΈη");
506 assert_eq!(interner.length(text2), 2);
507 }
508
509 #[test]
510 fn test_operations_with_empty_strings() {
511 let mut interner = StringInterner::new();
512
513 let empty = interner.intern("");
514 let text = interner.intern("test");
515
516 assert!(interner.starts_with(empty, empty));
517 assert!(interner.ends_with(empty, empty));
518 assert!(interner.contains(empty, empty));
519 assert!(interner.starts_with(text, empty));
520 assert!(interner.ends_with(text, empty));
521 assert!(interner.contains(text, empty));
522
523 let upper = interner.to_uppercase(empty);
524 let lower = interner.to_lowercase(empty);
525
526 assert_eq!(interner.get(upper), "");
527 assert_eq!(interner.get(lower), "");
528 assert_eq!(interner.length(empty), 0);
529 }
530
531 #[test]
532 fn test_char_at() {
533 let mut interner = StringInterner::new();
534
535 let hello = interner.intern("hello");
536
537 let h = interner.char_at(hello, 0);
538 assert_eq!(h.map(|handle| interner.get(handle)), Some("h"));
539
540 let e = interner.char_at(hello, 1);
541 assert_eq!(e.map(|handle| interner.get(handle)), Some("e"));
542
543 let o = interner.char_at(hello, 4);
544 assert_eq!(o.map(|handle| interner.get(handle)), Some("o"));
545
546 assert_eq!(interner.char_at(hello, 5), None); assert_eq!(interner.char_at(hello, 100), None); }
549
550 #[test]
551 fn test_char_at_unicode() {
552 let mut interner = StringInterner::new();
553
554 let text = interner.intern("Hello, δΈη!");
555 assert_eq!(
556 interner.char_at(text, 0).map(|h| interner.get(h)),
557 Some("H")
558 );
559 assert_eq!(
560 interner.char_at(text, 7).map(|h| interner.get(h)),
561 Some("δΈ")
562 );
563 assert_eq!(
564 interner.char_at(text, 8).map(|h| interner.get(h)),
565 Some("η")
566 );
567 assert_eq!(
568 interner.char_at(text, 9).map(|h| interner.get(h)),
569 Some("!")
570 );
571 assert_eq!(interner.char_at(text, 10), None);
572
573 let emoji = interner.intern("π¦π¦π¦");
574 assert_eq!(
575 interner.char_at(emoji, 0).map(|h| interner.get(h)),
576 Some("π¦")
577 );
578 assert_eq!(
579 interner.char_at(emoji, 1).map(|h| interner.get(h)),
580 Some("π¦")
581 );
582 assert_eq!(
583 interner.char_at(emoji, 2).map(|h| interner.get(h)),
584 Some("π¦")
585 );
586 assert_eq!(interner.char_at(emoji, 3), None);
587 }
588
589 #[test]
590 fn test_char_at_empty_string() {
591 let mut interner = StringInterner::new();
592
593 let empty = interner.intern("");
594 assert_eq!(interner.char_at(empty, 0), None);
595 }
596
597 #[test]
598 fn test_pad_start() {
599 let mut interner = StringInterner::new();
600
601 let hello = interner.intern("hello");
602 let padded = interner.pad_start(hello, 10, ' ');
603 assert_eq!(interner.get(padded), " hello");
604
605 let padded_stars = interner.pad_start(hello, 8, '*');
606 assert_eq!(interner.get(padded_stars), "***hello");
607
608 let no_pad = interner.pad_start(hello, 5, ' ');
610 assert_eq!(no_pad, hello);
611 assert_eq!(interner.get(no_pad), "hello");
612
613 let no_pad_long = interner.pad_start(hello, 3, ' ');
615 assert_eq!(no_pad_long, hello);
616 assert_eq!(interner.get(no_pad_long), "hello");
617 }
618
619 #[test]
620 fn test_pad_start_unicode() {
621 let mut interner = StringInterner::new();
622
623 let emoji = interner.intern("π¦");
624 let padded = interner.pad_start(emoji, 5, '-');
625 assert_eq!(interner.get(padded), "----π¦");
626
627 let text = interner.intern("δΈη");
628 let padded_text = interner.pad_start(text, 5, '0');
629 assert_eq!(interner.get(padded_text), "000δΈη");
630 }
631
632 #[test]
633 fn test_pad_end() {
634 let mut interner = StringInterner::new();
635
636 let hello = interner.intern("hello");
637 let padded = interner.pad_end(hello, 10, ' ');
638 assert_eq!(interner.get(padded), "hello ");
639
640 let padded_stars = interner.pad_end(hello, 8, '*');
641 assert_eq!(interner.get(padded_stars), "hello***");
642
643 let no_pad = interner.pad_end(hello, 5, ' ');
645 assert_eq!(no_pad, hello);
646 assert_eq!(interner.get(no_pad), "hello");
647
648 let no_pad_long = interner.pad_end(hello, 3, ' ');
650 assert_eq!(no_pad_long, hello);
651 assert_eq!(interner.get(no_pad_long), "hello");
652 }
653
654 #[test]
655 fn test_pad_end_unicode() {
656 let mut interner = StringInterner::new();
657
658 let emoji = interner.intern("π¦");
659 let padded = interner.pad_end(emoji, 5, '-');
660 assert_eq!(interner.get(padded), "π¦----");
661
662 let text = interner.intern("δΈη");
663 let padded_text = interner.pad_end(text, 5, '0');
664 assert_eq!(interner.get(padded_text), "δΈη000");
665 }
666
667 #[test]
668 fn test_trim() {
669 let mut interner = StringInterner::new();
670
671 let spaced = interner.intern(" hello ");
672 let trimmed = interner.trim(spaced);
673 assert_eq!(interner.get(trimmed), "hello");
674
675 let tab_newline = interner.intern("\t\nhello\n\t");
676 let trimmed2 = interner.trim(tab_newline);
677 assert_eq!(interner.get(trimmed2), "hello");
678
679 let no_space = interner.intern("hello");
681 let trimmed3 = interner.trim(no_space);
682 assert_eq!(interner.get(trimmed3), "hello");
683
684 let only_space = interner.intern(" ");
686 let trimmed4 = interner.trim(only_space);
687 assert_eq!(interner.get(trimmed4), "");
688
689 let empty = interner.intern("");
691 let trimmed5 = interner.trim(empty);
692 assert_eq!(interner.get(trimmed5), "");
693 }
694
695 #[test]
696 fn test_trim_unicode() {
697 let mut interner = StringInterner::new();
698
699 let text = interner.intern(" Hello, δΈη! ");
700 let trimmed = interner.trim(text);
701 assert_eq!(interner.get(trimmed), "Hello, δΈη!");
702
703 let emoji = interner.intern("\n\tπ¦π¦\t\n");
704 let trimmed_emoji = interner.trim(emoji);
705 assert_eq!(interner.get(trimmed_emoji), "π¦π¦");
706 }
707
708 #[test]
709 fn test_trim_start() {
710 let mut interner = StringInterner::new();
711
712 let spaced = interner.intern(" hello ");
713 let trimmed = interner.trim_start(spaced);
714 assert_eq!(interner.get(trimmed), "hello ");
715
716 let tab_newline = interner.intern("\t\nhello");
717 let trimmed2 = interner.trim_start(tab_newline);
718 assert_eq!(interner.get(trimmed2), "hello");
719
720 let no_space = interner.intern("hello ");
722 let trimmed3 = interner.trim_start(no_space);
723 assert_eq!(interner.get(trimmed3), "hello ");
724
725 let only_space = interner.intern(" ");
727 let trimmed4 = interner.trim_start(only_space);
728 assert_eq!(interner.get(trimmed4), "");
729 }
730
731 #[test]
732 fn test_trim_start_unicode() {
733 let mut interner = StringInterner::new();
734
735 let text = interner.intern(" π¦ Rust ");
736 let trimmed = interner.trim_start(text);
737 assert_eq!(interner.get(trimmed), "π¦ Rust ");
738 }
739
740 #[test]
741 fn test_trim_end() {
742 let mut interner = StringInterner::new();
743
744 let spaced = interner.intern(" hello ");
745 let trimmed = interner.trim_end(spaced);
746 assert_eq!(interner.get(trimmed), " hello");
747
748 let tab_newline = interner.intern("hello\n\t");
749 let trimmed2 = interner.trim_end(tab_newline);
750 assert_eq!(interner.get(trimmed2), "hello");
751
752 let no_space = interner.intern(" hello");
754 let trimmed3 = interner.trim_end(no_space);
755 assert_eq!(interner.get(trimmed3), " hello");
756
757 let only_space = interner.intern(" ");
759 let trimmed4 = interner.trim_end(only_space);
760 assert_eq!(interner.get(trimmed4), "");
761 }
762
763 #[test]
764 fn test_trim_end_unicode() {
765 let mut interner = StringInterner::new();
766
767 let text = interner.intern(" Rust π¦ ");
768 let trimmed = interner.trim_end(text);
769 assert_eq!(interner.get(trimmed), " Rust π¦");
770 }
771}