soroban_sdk/
symbol.rs

1use core::{cmp::Ordering, convert::Infallible, fmt::Debug};
2
3use super::{
4    env::internal::{Env as _, Symbol as SymbolVal, SymbolSmall},
5    ConversionError, Env, TryFromVal, TryIntoVal, Val,
6};
7
8#[cfg(not(target_family = "wasm"))]
9use super::env::SymbolStr;
10
11#[cfg(not(target_family = "wasm"))]
12use crate::env::internal::xdr::{ScSymbol, ScVal};
13use crate::{
14    env::MaybeEnv,
15    unwrap::{UnwrapInfallible, UnwrapOptimized},
16};
17
18/// Symbol is a short string with a limited character set.
19///
20/// Valid characters are `a-zA-Z0-9_` and maximum length is 32 characters.
21///
22/// Symbols are used for the for symbolic identifiers, such as function
23/// names and user-defined structure field/enum variant names. That's why
24/// these idenfiers have limited length.
25///
26/// While Symbols up to 32 characters long are allowed, Symbols that are 9
27/// characters long or shorter are more efficient at runtime and also can be
28/// computed at compile time.
29#[derive(Clone)]
30pub struct Symbol {
31    env: MaybeEnv,
32    val: SymbolVal,
33}
34
35impl Debug for Symbol {
36    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
37        #[cfg(target_family = "wasm")]
38        write!(f, "Symbol(..)")?;
39        #[cfg(not(target_family = "wasm"))]
40        write!(f, "Symbol({})", self.to_string())?;
41        Ok(())
42    }
43}
44
45impl Eq for Symbol {}
46
47impl PartialEq for Symbol {
48    fn eq(&self, other: &Self) -> bool {
49        self.partial_cmp(other) == Some(Ordering::Equal)
50    }
51}
52
53impl PartialOrd for Symbol {
54    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
55        Some(Ord::cmp(self, other))
56    }
57}
58
59impl Ord for Symbol {
60    fn cmp(&self, other: &Self) -> Ordering {
61        let self_raw = self.val.to_val();
62        let other_raw = other.val.to_val();
63
64        match (
65            SymbolSmall::try_from(self_raw),
66            SymbolSmall::try_from(other_raw),
67        ) {
68            // Compare small symbols.
69            (Ok(self_sym), Ok(other_sym)) => self_sym.cmp(&other_sym),
70            // The object-to-small symbol comparisons are handled by `obj_cmp`,
71            // so it's safe to handle all the other cases using it.
72            _ => {
73                let (e1, e2): (Result<Env, _>, Result<Env, _>) =
74                    (self.env.clone().try_into(), other.env.clone().try_into());
75                match (e1, e2) {
76                    (Err(_), Err(_)) => {
77                        panic!("symbol object is missing the env reference");
78                    }
79                    (Err(_), Ok(e)) | (Ok(e), Err(_)) => {
80                        let v = e.obj_cmp(self_raw, other_raw).unwrap_infallible();
81                        v.cmp(&0)
82                    }
83                    #[cfg(not(target_family = "wasm"))]
84                    (Ok(e1), Ok(e2)) => {
85                        if !e1.is_same_env(&e2) {
86                            return ScVal::from(self).cmp(&ScVal::from(other));
87                        }
88                        let v = e1.obj_cmp(self_raw, other_raw).unwrap_infallible();
89                        v.cmp(&0)
90                    }
91                    #[cfg(target_family = "wasm")]
92                    (Ok(e), Ok(_)) => {
93                        let v = e.obj_cmp(self_raw, other_raw).unwrap_infallible();
94                        v.cmp(&0)
95                    }
96                }
97            }
98        }
99    }
100}
101
102impl TryFromVal<Env, SymbolVal> for Symbol {
103    type Error = Infallible;
104
105    fn try_from_val(env: &Env, val: &SymbolVal) -> Result<Self, Self::Error> {
106        Ok(unsafe { Symbol::unchecked_new(env.clone(), *val) })
107    }
108}
109
110impl TryFromVal<Env, Val> for Symbol {
111    type Error = ConversionError;
112
113    fn try_from_val(env: &Env, val: &Val) -> Result<Self, Self::Error> {
114        Ok(SymbolVal::try_from_val(env, val)?
115            .try_into_val(env)
116            .unwrap_infallible())
117    }
118}
119
120impl TryFromVal<Env, Symbol> for Val {
121    type Error = ConversionError;
122
123    fn try_from_val(_env: &Env, v: &Symbol) -> Result<Self, Self::Error> {
124        Ok(v.to_val())
125    }
126}
127
128impl TryFromVal<Env, &Symbol> for Val {
129    type Error = ConversionError;
130
131    fn try_from_val(_env: &Env, v: &&Symbol) -> Result<Self, Self::Error> {
132        Ok(v.to_val())
133    }
134}
135
136impl TryFromVal<Env, &str> for Symbol {
137    type Error = ConversionError;
138
139    fn try_from_val(env: &Env, val: &&str) -> Result<Self, Self::Error> {
140        Ok(SymbolVal::try_from_val(env, val)?
141            .try_into_val(env)
142            .unwrap_infallible())
143    }
144}
145
146#[cfg(not(target_family = "wasm"))]
147impl From<&Symbol> for ScVal {
148    fn from(v: &Symbol) -> Self {
149        // This conversion occurs only in test utilities, and theoretically all
150        // values should convert to an ScVal because the Env won't let the host
151        // type to exist otherwise, unwrapping. Even if there are edge cases
152        // that don't, this is a trade off for a better test developer
153        // experience.
154        if let Ok(ss) = SymbolSmall::try_from(v.val) {
155            ScVal::try_from(ss).unwrap()
156        } else {
157            let e: Env = v.env.clone().try_into().unwrap();
158            ScVal::try_from_val(&e, &v.to_val()).unwrap()
159        }
160    }
161}
162
163#[cfg(not(target_family = "wasm"))]
164impl From<Symbol> for ScVal {
165    fn from(v: Symbol) -> Self {
166        (&v).into()
167    }
168}
169
170#[cfg(not(target_family = "wasm"))]
171impl TryFromVal<Env, Symbol> for ScVal {
172    type Error = ConversionError;
173    fn try_from_val(_e: &Env, v: &Symbol) -> Result<Self, ConversionError> {
174        Ok(v.into())
175    }
176}
177
178#[cfg(not(target_family = "wasm"))]
179impl TryFromVal<Env, ScVal> for Symbol {
180    type Error = ConversionError;
181    fn try_from_val(env: &Env, val: &ScVal) -> Result<Self, Self::Error> {
182        Ok(SymbolVal::try_from_val(env, &Val::try_from_val(env, val)?)?
183            .try_into_val(env)
184            .unwrap_infallible())
185    }
186}
187
188#[cfg(not(target_family = "wasm"))]
189impl TryFromVal<Env, ScSymbol> for Symbol {
190    type Error = ConversionError;
191    fn try_from_val(env: &Env, val: &ScSymbol) -> Result<Self, Self::Error> {
192        Ok(SymbolVal::try_from_val(env, val)?
193            .try_into_val(env)
194            .unwrap_infallible())
195    }
196}
197
198impl Symbol {
199    /// Creates a new Symbol given a string with valid characters.
200    ///
201    /// Valid characters are `a-zA-Z0-9_` and maximum string length is 32
202    /// characters.
203    ///
204    /// Use `symbol_short!` for constant symbols that are 9 characters or less.
205    ///
206    /// Use `Symbol::try_from_val(env, s)`/`s.try_into_val(env)` in case if
207    /// failures need to be handled gracefully.
208    ///
209    /// ### Panics
210    ///
211    /// When the input string is not representable by Symbol.
212    pub fn new(env: &Env, s: &str) -> Self {
213        Self {
214            env: env.clone().into(),
215            val: s.try_into_val(env).unwrap_optimized(),
216        }
217    }
218
219    /// Creates a new Symbol given a short string with valid characters.
220    ///
221    /// Valid characters are `a-zA-Z0-9_` and maximum length is 9 characters.
222    ///
223    /// The conversion can happen at compile time if called in a const context,
224    /// such as:
225    ///
226    /// ```rust
227    /// # use soroban_sdk::Symbol;
228    /// const SYMBOL: Symbol = Symbol::short("abcde");
229    /// ```
230    ///
231    /// Note that when called from a non-const context the conversion will occur
232    /// at runtime and the conversion logic will add considerable number of
233    /// bytes to built wasm file. For this reason the function should be generally
234    /// avoided:
235    ///
236    /// ```rust
237    /// # use soroban_sdk::Symbol;
238    /// let SYMBOL: Symbol = Symbol::short("abcde"); // AVOID!
239    /// ```
240    ///
241    /// Instead use the `symbol_short!()` macro that will ensure the conversion always occurs in a const-context:
242    ///
243    /// ```rust
244    /// # use soroban_sdk::{symbol_short, Symbol};
245    /// let SYMBOL: Symbol = symbol_short!("abcde"); // 👍
246    /// ```
247    ///
248    /// ### Panics
249    ///
250    /// When the input string is not representable by Symbol.
251    #[doc(hidden)]
252    #[deprecated(note = "use [symbol_short!()]")]
253    pub const fn short(s: &str) -> Self {
254        if let Ok(sym) = SymbolSmall::try_from_str(s) {
255            Symbol {
256                env: MaybeEnv::none(),
257                val: SymbolVal::from_small(sym),
258            }
259        } else {
260            panic!("short symbols are limited to 9 characters");
261        }
262    }
263
264    #[inline(always)]
265    pub(crate) unsafe fn unchecked_new(env: Env, val: SymbolVal) -> Self {
266        Self {
267            env: env.into(),
268            val,
269        }
270    }
271
272    pub fn as_val(&self) -> &Val {
273        self.val.as_val()
274    }
275
276    pub fn to_val(&self) -> Val {
277        self.val.to_val()
278    }
279
280    pub fn to_symbol_val(&self) -> SymbolVal {
281        self.val
282    }
283}
284
285#[cfg(not(target_family = "wasm"))]
286extern crate std;
287#[cfg(not(target_family = "wasm"))]
288impl ToString for Symbol {
289    fn to_string(&self) -> String {
290        if let Ok(s) = SymbolSmall::try_from(self.val) {
291            s.to_string()
292        } else {
293            let e: Env = self.env.clone().try_into().unwrap_optimized();
294            SymbolStr::try_from_val(&e, &self.val)
295                .unwrap_optimized()
296                .to_string()
297        }
298    }
299}