ion_rs/
symbol_ref.rs

1use crate::Symbol;
2use std::borrow::Borrow;
3use std::fmt::{Debug, Formatter};
4use std::hash::{Hash, Hasher};
5
6/// A reference to a fully resolved symbol. Like `Symbol` (a fully resolved symbol with a
7/// static lifetime), a `SymbolRef` may have known or undefined text (i.e. `$0`).
8#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)]
9pub struct SymbolRef<'a> {
10    text: Option<&'a str>,
11}
12
13impl<'a> Debug for SymbolRef<'a> {
14    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
15        write!(f, "{}", self.text.unwrap_or("$0"))
16    }
17}
18
19impl<'a> SymbolRef<'a> {
20    /// If this symbol has known text, returns `Some(&str)`. Otherwise, returns `None`.
21    pub fn text(&self) -> Option<&str> {
22        self.text
23    }
24
25    /// Constructs a `SymbolRef` with unknown text.
26    pub fn with_unknown_text() -> Self {
27        SymbolRef { text: None }
28    }
29
30    /// Constructs a `SymbolRef` with the specified text.
31    pub fn with_text(text: &str) -> SymbolRef {
32        SymbolRef { text: Some(text) }
33    }
34
35    pub fn to_owned(self) -> Symbol {
36        match self.text() {
37            None => Symbol::unknown_text(),
38            Some(text) => Symbol::owned(text),
39        }
40    }
41}
42
43impl<'a, A> PartialEq<A> for SymbolRef<'a>
44where
45    A: AsSymbolRef,
46{
47    fn eq(&self, other: &A) -> bool {
48        let other_symbol_ref = other.as_symbol_ref();
49        self == &other_symbol_ref
50    }
51}
52
53/// Allows a `SymbolRef` to be constructed from a source value. This enables non-symbol types to be
54/// viewed as a symbol with little to no runtime overhead.
55pub trait AsSymbolRef {
56    fn as_symbol_ref(&self) -> SymbolRef;
57}
58
59// All text types can be viewed as a `SymbolRef`.
60impl<'a, A: AsRef<str> + 'a> AsSymbolRef for A {
61    fn as_symbol_ref(&self) -> SymbolRef {
62        SymbolRef {
63            text: Some(self.as_ref()),
64        }
65    }
66}
67
68impl<'a> Hash for SymbolRef<'a> {
69    fn hash<H: Hasher>(&self, state: &mut H) {
70        match self.text {
71            None => 0.hash(state),
72            Some(text) => text.hash(state),
73        }
74    }
75}
76
77impl<'a> From<&'a str> for SymbolRef<'a> {
78    fn from(text: &'a str) -> Self {
79        Self { text: Some(text) }
80    }
81}
82
83impl<'a> From<&'a Symbol> for SymbolRef<'a> {
84    fn from(symbol: &'a Symbol) -> Self {
85        Self {
86            text: symbol.text(),
87        }
88    }
89}
90
91// Note that this method panics if the SymbolRef has unknown text! This is unfortunate but is required
92// in order to allow a HashMap<SymbolRef, _> to do lookups with a &str instead of a &SymbolRef
93impl<'a> Borrow<str> for SymbolRef<'a> {
94    fn borrow(&self) -> &str {
95        self.text()
96            .expect("cannot borrow a &str from a SymbolRef with unknown text")
97    }
98}
99
100// Owned `Symbol` values can be viewed as a `SymbolRef`. Due to lifetime conflicts in the
101// trait definitions, this cannot be achieved with `AsRef` or `Borrow`.
102impl AsSymbolRef for Symbol {
103    fn as_symbol_ref(&self) -> SymbolRef {
104        self.text()
105            .map(SymbolRef::with_text)
106            .unwrap_or_else(SymbolRef::with_unknown_text)
107    }
108}
109
110impl AsSymbolRef for &Symbol {
111    fn as_symbol_ref(&self) -> SymbolRef {
112        self.text()
113            .map(SymbolRef::with_text)
114            .unwrap_or_else(SymbolRef::with_unknown_text)
115    }
116}
117
118impl<'borrow, 'data> AsSymbolRef for &'borrow SymbolRef<'data> {
119    fn as_symbol_ref(&self) -> SymbolRef<'data> {
120        // This is essentially free; the only data inside is an Option<&str>
121        (*self).clone()
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128
129    #[test]
130    fn symbol_ref_with_text() {
131        let symbol_ref = SymbolRef::with_text("foo");
132        assert_eq!(Some("foo"), symbol_ref.text());
133    }
134
135    #[test]
136    fn symbol_ref_with_unknown_text() {
137        let symbol_ref = SymbolRef::with_unknown_text();
138        assert_eq!(None, symbol_ref.text());
139    }
140
141    #[test]
142    fn str_as_symbol_ref() {
143        let symbol_ref: SymbolRef = "foo".as_symbol_ref();
144        assert_eq!(Some("foo"), symbol_ref.text());
145    }
146
147    #[test]
148    fn symbol_as_symbol_ref() {
149        let symbol = Symbol::owned("foo");
150        let symbol_ref: SymbolRef = symbol.as_symbol_ref();
151        assert_eq!(Some("foo"), symbol_ref.text());
152    }
153
154    #[test]
155    fn symbol_with_unknown_text_as_symbol_ref() {
156        let symbol = Symbol::unknown_text();
157        let symbol_ref: SymbolRef = symbol.as_symbol_ref();
158        assert_eq!(None, symbol_ref.text());
159    }
160}