1#![deny(unsafe_op_in_unsafe_fn, rust_2018_idioms)]
2#![warn(missing_docs)]
3#![doc = include_str!("../README.md")]
4
5use std::{
6 cmp::Ordering,
7 error::Error,
8 fmt::{self, Debug, Display, Formatter},
9 hash::{Hash, Hasher},
10 num::NonZeroU64,
11 ops::Deref,
12 ptr, str,
13};
14
15#[repr(transparent)]
17#[derive(Clone, Copy, PartialEq, Eq, Hash)]
18pub struct Symbol {
19 data: NonZeroU64,
20}
21
22impl Symbol {
23 pub fn from_str<T: AsRef<str>>(symbol: T) -> Result<Self, InvalidSymbol> {
30 #[inline]
31 fn from_str_inner(symbol: &str) -> Result<Symbol, InvalidSymbol> {
32 let len = symbol.len();
33
34 if len.wrapping_sub(1) < 7 {
35 Ok(unsafe { Symbol::from_str_unchecked(symbol) })
40 } else {
41 Err(InvalidSymbol)
42 }
43 }
44
45 from_str_inner(symbol.as_ref())
46 }
47
48 #[inline]
55 pub unsafe fn from_str_unchecked(symbol: &str) -> Self {
56 unsafe { Self::from_bytes_unchecked(symbol.as_bytes()) }
59 }
60
61 #[inline]
69 pub unsafe fn from_bytes_unchecked(symbol: &[u8]) -> Self {
70 let mut bytes = [0u8; 8];
71 let len = symbol.len();
72 bytes[7] = len as u8;
73
74 unsafe { ptr::copy_nonoverlapping(symbol.as_ptr(), bytes.as_mut_ptr(), len) };
79
80 Self {
81 data: unsafe { NonZeroU64::new_unchecked(u64::from_ne_bytes(bytes)) },
84 }
85 }
86
87 #[inline]
92 pub fn as_str(&self) -> &str {
93 let bytes = unsafe { &*(&self.data as *const NonZeroU64).cast::<[u8; 8]>() };
98
99 let len = usize::from(bytes[7]);
100
101 let str_bytes = unsafe { bytes.get_unchecked(..len) };
104
105 unsafe { str::from_utf8_unchecked(str_bytes) }
109 }
110}
111
112impl Debug for Symbol {
113 #[inline]
114 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
115 write!(f, "Symbol({})", self.as_str())
116 }
117}
118
119impl Display for Symbol {
120 #[inline]
121 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
122 f.pad(self.as_str())
123 }
124}
125
126impl AsRef<str> for Symbol {
127 #[inline]
128 fn as_ref(&self) -> &str {
129 self.as_str()
130 }
131}
132
133impl Deref for Symbol {
134 type Target = str;
135
136 #[inline]
137 fn deref(&self) -> &Self::Target {
138 self.as_str()
139 }
140}
141
142impl PartialEq<str> for Symbol {
143 #[inline]
144 fn eq(&self, other: &str) -> bool {
145 self.as_str() == other
146 }
147}
148
149impl PartialEq<Symbol> for str {
150 #[inline]
151 fn eq(&self, other: &Symbol) -> bool {
152 other == self
153 }
154}
155
156impl PartialEq<&str> for Symbol {
157 #[inline]
158 fn eq(&self, other: &&str) -> bool {
159 self == *other
160 }
161}
162
163impl PartialEq<Symbol> for &str {
164 #[inline]
165 fn eq(&self, other: &Symbol) -> bool {
166 *self == other
167 }
168}
169
170impl PartialEq<String> for Symbol {
171 #[inline]
172 fn eq(&self, other: &String) -> bool {
173 self == &**other
174 }
175}
176
177impl PartialEq<Symbol> for String {
178 #[inline]
179 fn eq(&self, other: &Symbol) -> bool {
180 self.as_str() == other
181 }
182}
183
184impl PartialOrd for Symbol {
185 #[inline]
186 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
187 let this = u64::from_be(self.data.get());
188 let other = u64::from_be(other.data.get());
189 this.partial_cmp(&other)
190 }
191}
192
193impl Ord for Symbol {
194 #[inline]
195 fn cmp(&self, other: &Self) -> Ordering {
196 let this = u64::from_be(self.data.get());
197 let other = u64::from_be(other.data.get());
198 this.cmp(&other)
199 }
200}
201
202#[derive(Debug, Clone, Copy, PartialEq, Eq)]
205pub struct InvalidSymbol;
206
207impl Display for InvalidSymbol {
208 #[inline]
209 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
210 f.write_str("Invalid symbol length; length must be greater than 0 and less than 8")
211 }
212}
213
214impl Error for InvalidSymbol {}
215
216#[derive(Clone)]
219pub enum LongSymbol {
220 Inline(Symbol),
222 Heap(Box<str>),
224}
225
226impl LongSymbol {
227 #[inline]
230 pub fn to_symbol(&self) -> Option<Symbol> {
231 match self {
232 Self::Inline(symbol) => Some(*symbol),
233 Self::Heap(string) => Symbol::from_str(&**string).ok(),
234 }
235 }
236
237 #[inline]
239 pub fn as_str(&self) -> &str {
240 match self {
241 Self::Inline(symbol) => symbol.as_str(),
242 Self::Heap(string) => &**string,
243 }
244 }
245}
246
247impl From<Symbol> for LongSymbol {
248 #[inline]
249 fn from(value: Symbol) -> Self {
250 Self::Inline(value)
251 }
252}
253
254impl From<String> for LongSymbol {
255 #[inline]
256 fn from(value: String) -> Self {
257 match Symbol::from_str(value.as_str()) {
258 Ok(symbol) => Self::Inline(symbol),
259 Err(_) => Self::Heap(value.into_boxed_str()),
260 }
261 }
262}
263
264impl From<Box<str>> for LongSymbol {
265 #[inline]
266 fn from(value: Box<str>) -> Self {
267 match Symbol::from_str(&*value) {
268 Ok(symbol) => Self::Inline(symbol),
269 Err(_) => Self::Heap(value),
270 }
271 }
272}
273
274impl From<&str> for LongSymbol {
275 #[inline]
276 fn from(value: &str) -> Self {
277 match Symbol::from_str(value) {
278 Ok(symbol) => Self::Inline(symbol),
279 Err(_) => Self::Heap(Box::from(value)),
280 }
281 }
282}
283
284impl From<LongSymbol> for String {
285 #[inline]
286 fn from(value: LongSymbol) -> Self {
287 match value {
288 LongSymbol::Inline(symbol) => symbol.as_str().to_owned(),
289 LongSymbol::Heap(string) => String::from(string),
290 }
291 }
292}
293
294impl From<LongSymbol> for Box<str> {
295 #[inline]
296 fn from(value: LongSymbol) -> Self {
297 match value {
298 LongSymbol::Inline(symbol) => Box::from(symbol.as_str()),
299 LongSymbol::Heap(string) => string,
300 }
301 }
302}
303
304impl Debug for LongSymbol {
305 #[inline]
306 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
307 write!(f, "LongSymbol({})", self.as_str())
308 }
309}
310
311impl Display for LongSymbol {
312 #[inline]
313 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
314 f.pad(self.as_str())
315 }
316}
317
318impl AsRef<str> for LongSymbol {
319 #[inline]
320 fn as_ref(&self) -> &str {
321 self.as_str()
322 }
323}
324
325impl Deref for LongSymbol {
326 type Target = str;
327
328 #[inline]
329 fn deref(&self) -> &Self::Target {
330 self.as_str()
331 }
332}
333
334impl PartialEq<Symbol> for LongSymbol {
335 #[inline]
336 fn eq(&self, other: &Symbol) -> bool {
337 match self {
338 LongSymbol::Inline(symbol) => symbol == other,
339 LongSymbol::Heap(string) => &**string == other,
340 }
341 }
342}
343
344impl PartialEq<LongSymbol> for Symbol {
345 #[inline]
346 fn eq(&self, other: &LongSymbol) -> bool {
347 other == self
348 }
349}
350
351impl PartialEq<str> for LongSymbol {
352 #[inline]
353 fn eq(&self, other: &str) -> bool {
354 match self {
355 LongSymbol::Inline(symbol) => symbol == other,
356 LongSymbol::Heap(string) => &**string == other,
357 }
358 }
359}
360
361impl PartialEq<LongSymbol> for str {
362 #[inline]
363 fn eq(&self, other: &LongSymbol) -> bool {
364 other == self
365 }
366}
367
368impl PartialEq<String> for LongSymbol {
369 #[inline]
370 fn eq(&self, other: &String) -> bool {
371 match self {
372 LongSymbol::Inline(symbol) => symbol == other,
373 LongSymbol::Heap(string) => &**string == &**other,
374 }
375 }
376}
377
378impl PartialEq<LongSymbol> for String {
379 #[inline]
380 fn eq(&self, other: &LongSymbol) -> bool {
381 other == self
382 }
383}
384
385impl PartialEq<&str> for LongSymbol {
386 #[inline]
387 fn eq(&self, other: &&str) -> bool {
388 match self {
389 LongSymbol::Inline(symbol) => symbol == *other,
390 LongSymbol::Heap(string) => &**string == *other,
391 }
392 }
393}
394
395impl PartialEq<LongSymbol> for &str {
396 #[inline]
397 fn eq(&self, other: &LongSymbol) -> bool {
398 other == self
399 }
400}
401
402impl PartialEq for LongSymbol {
403 #[inline]
404 fn eq(&self, other: &Self) -> bool {
405 match self {
406 Self::Inline(symbol) => symbol == other,
407 Self::Heap(string) => &**string == other,
408 }
409 }
410}
411
412impl Eq for LongSymbol {}
413
414#[inline]
415fn long_symbol_cmp(a: &LongSymbol, b: &LongSymbol) -> Ordering {
416 match (a, b) {
417 (LongSymbol::Inline(a), LongSymbol::Inline(b)) => a.cmp(b),
418 _ => a.as_str().cmp(b.as_str()),
419 }
420}
421
422impl PartialOrd for LongSymbol {
423 #[inline]
424 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
425 Some(long_symbol_cmp(self, other))
426 }
427}
428
429impl Ord for LongSymbol {
430 #[inline]
431 fn cmp(&self, other: &Self) -> Ordering {
432 long_symbol_cmp(self, other)
433 }
434}
435
436impl Hash for LongSymbol {
437 fn hash<H: Hasher>(&self, state: &mut H) {
438 Hash::hash(self.as_str(), state)
439 }
440}
441
442#[cfg(feature = "serde")]
443mod serde {
444 use super::*;
445 use ::serde::{
446 de::{self, Visitor},
447 Deserialize, Deserializer, Serialize, Serializer,
448 };
449
450 impl Serialize for Symbol {
451 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
452 where
453 S: Serializer,
454 {
455 serializer.serialize_str(self.as_str())
456 }
457 }
458
459 impl<'de> Deserialize<'de> for Symbol {
460 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
461 where
462 D: Deserializer<'de>,
463 {
464 deserializer.deserialize_str(SymbolVisitor)
465 }
466 }
467
468 struct SymbolVisitor;
469
470 impl<'de> Visitor<'de> for SymbolVisitor {
471 type Value = Symbol;
472
473 #[inline]
474 fn expecting(&self, f: &mut Formatter<'_>) -> fmt::Result {
475 f.write_str("A symbol string")
476 }
477
478 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
479 where
480 E: de::Error,
481 {
482 Symbol::from_str(v).map_err(E::custom)
483 }
484 }
485
486 impl Serialize for LongSymbol {
487 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
488 where
489 S: Serializer,
490 {
491 serializer.serialize_str(self.as_str())
492 }
493 }
494
495 impl<'de> Deserialize<'de> for LongSymbol {
496 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
497 where
498 D: Deserializer<'de>,
499 {
500 deserializer.deserialize_str(LongSymbolVisitor)
501 }
502 }
503
504 struct LongSymbolVisitor;
505
506 impl<'de> Visitor<'de> for LongSymbolVisitor {
507 type Value = LongSymbol;
508
509 #[inline]
510 fn expecting(&self, f: &mut Formatter<'_>) -> fmt::Result {
511 f.write_str("A string")
512 }
513
514 fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
515 where
516 E: de::Error,
517 {
518 Ok(LongSymbol::from(v))
519 }
520
521 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
522 where
523 E: de::Error,
524 {
525 Ok(LongSymbol::from(v))
526 }
527 }
528}
529
530#[cfg(feature = "sqlx")]
531mod sqlx {
532 use super::*;
533 use sqlx_core::{
534 database::{Database, HasValueRef},
535 decode::Decode,
536 error::BoxDynError,
537 types::Type,
538 };
539
540 impl<'r, DB: Database> Decode<'r, DB> for Symbol
541 where
542 &'r str: Decode<'r, DB>,
543 {
544 fn decode(value: <DB as HasValueRef<'r>>::ValueRef) -> Result<Self, BoxDynError> {
545 let value = <&str as Decode<DB>>::decode(value)?;
546 Self::from_str(value).map_err(Into::into)
547 }
548 }
549
550 impl<DB: Database> Type<DB> for Symbol
551 where
552 str: Type<DB>,
553 {
554 fn type_info() -> <DB as Database>::TypeInfo {
555 <str as Type<DB>>::type_info()
556 }
557 }
558
559 impl<'r, DB: Database> Decode<'r, DB> for LongSymbol
560 where
561 &'r str: Decode<'r, DB>,
562 {
563 fn decode(value: <DB as HasValueRef<'r>>::ValueRef) -> Result<Self, BoxDynError> {
564 let value = <&str as Decode<DB>>::decode(value)?;
565 Ok(LongSymbol::from(value))
566 }
567 }
568
569 impl<DB: Database> Type<DB> for LongSymbol
570 where
571 str: Type<DB>,
572 {
573 fn type_info() -> <DB as Database>::TypeInfo {
574 <str as Type<DB>>::type_info()
575 }
576 }
577}
578
579#[cfg(test)]
580mod tests {
581 use super::*;
582
583 #[test]
584 fn sizes() {
585 use std::mem::size_of;
586
587 assert_eq!(size_of::<Symbol>(), 8);
588 assert_eq!(size_of::<Option<Symbol>>(), 8);
589 assert_eq!(size_of::<Result<Symbol, InvalidSymbol>>(), 8);
590
591 assert_eq!(size_of::<LongSymbol>(), 16);
592 }
593
594 #[test]
595 fn creation() {
596 assert!(Symbol::from_str("AAPL").is_ok());
597 assert!(Symbol::from_str("HSBC.A").is_ok());
598 assert!(Symbol::from_str("this is too long").is_err());
599 assert!(Symbol::from_str("").is_err());
600
601 assert!(matches!(LongSymbol::from("AAPL"), LongSymbol::Inline(_)));
602 assert!(matches!(
603 LongSymbol::from("this is too long"),
604 LongSymbol::Heap(_)
605 ));
606 assert!(matches!(LongSymbol::from(""), LongSymbol::Heap(_)));
607 }
608
609 #[test]
610 fn equality() {
611 let sym1 = Symbol::from_str("ABCDEFG").unwrap();
612 let sym2 = Symbol::from_str("ABCDEFG").unwrap();
613 let sym3 = Symbol::from_str("ABCDEF").unwrap();
614
615 assert_eq!(sym1, sym2);
616 assert_ne!(sym1, sym3);
617 assert_ne!(sym2, sym3);
618
619 let lsym1 = LongSymbol::from(sym1);
620 let lsym2 = LongSymbol::Heap(Box::from("ABCDEFG"));
621 let lsym3 = LongSymbol::from(sym3);
622
623 assert_eq!(lsym1, lsym2);
624 assert_eq!(sym1, lsym2);
625 assert_eq!(lsym1, sym2);
626 assert_ne!(sym1, lsym3);
627 assert_ne!(sym2, lsym3);
628 assert_eq!(sym3, lsym3);
629 }
630
631 #[test]
632 fn str_equality() {
633 let sym1 = Symbol::from_str("ABCDEFG").unwrap();
634 let sym2 = Symbol::from_str("ABCDEF").unwrap();
635 let str1 = "ABCDEFG";
636 let str2 = "ABCDEF";
637
638 assert_eq!(sym1, str1);
639 assert_eq!(sym2, str2);
640 assert_ne!(sym1, str2);
641 assert_ne!(sym2, str1);
642
643 let lsym1 = LongSymbol::from("this is too long");
644 assert_eq!(lsym1, "this is too long");
645 assert_ne!(lsym1, "short");
646 }
647
648 #[test]
649 fn ord() {
650 let symbols = ["A", "AA", "AB", "B", "BBB", "C", "CA", "CBS"]
651 .map(|symbol| Symbol::from_str(symbol).unwrap());
652
653 for (i, sym1) in symbols.into_iter().enumerate() {
654 for (j, sym2) in symbols.into_iter().enumerate() {
655 assert_eq!(i.cmp(&j), sym1.cmp(&sym2));
656 }
657 }
658
659 let long_symbols = [
660 "",
661 "A",
662 "AA",
663 "AB",
664 "ABRACADABRA",
665 "B",
666 "BBB",
667 "BBBBBBBB",
668 "C",
669 "CA",
670 "CALIFORNIA",
671 "CBS",
672 "VERYLONGSYMBOL",
673 ]
674 .map(LongSymbol::from);
675
676 for (i, sym1) in long_symbols.iter().enumerate() {
677 for (j, sym2) in long_symbols.iter().enumerate() {
678 assert_eq!(i.cmp(&j), sym1.cmp(sym2));
679 }
680 }
681 }
682
683 #[test]
684 fn formatting() {
685 let symbol = Symbol::from_str("FOO").unwrap();
686
687 assert_eq!(format!("{symbol}"), "FOO");
688 assert_eq!(format!("{symbol:<5}"), "FOO ");
689 assert_eq!(format!("{symbol:>5}"), " FOO");
690 assert_eq!(format!("{symbol:^5}"), " FOO ");
691
692 let long_symbol = LongSymbol::from("LONGSYMBOL");
693 assert_eq!(format!("{long_symbol}"), "LONGSYMBOL");
694 assert_eq!(format!("{long_symbol:<16}"), "LONGSYMBOL ");
695 assert_eq!(format!("{long_symbol:>16}"), " LONGSYMBOL");
696 assert_eq!(format!("{long_symbol:^16}"), " LONGSYMBOL ");
697 }
698}