jlrs/data/managed/symbol/
mod.rs

1//! Managed type for `Symbol`. Symbols represent identifiers like module and function names.
2
3use std::{
4    ffi::CStr,
5    hash::{Hash, Hasher},
6    marker::PhantomData,
7    ptr::NonNull,
8};
9
10use jl_sys::{jl_gensym, jl_sym_t, jl_symbol_n, jl_symbol_type, jl_tagged_gensym};
11use jlrs_sys::{jlrs_symbol_hash, jlrs_symbol_name};
12use rustc_hash::{FxBuildHasher, FxHashMap};
13
14use self::static_symbol::{StaticSymbol, Sym};
15use super::Weak;
16use crate::{
17    catch::{catch_exceptions, unwrap_exc},
18    data::{
19        cache::Cache,
20        managed::{erase_scope_lifetime, private::ManagedPriv},
21    },
22    error::{JlrsError, JlrsResult},
23    impl_julia_typecheck,
24    memory::target::{Target, TargetException, TargetResult, unrooted::Unrooted},
25    private::Private,
26};
27
28pub mod static_symbol;
29
30static CACHE: SymbolCache = SymbolCache::new();
31
32/// `Symbol`s are used Julia to represent identifiers, `:x` represents the `Symbol` `x`. Things
33/// that can be accessed using a `Symbol` include submodules, functions, and globals. However,
34/// the methods that provide this functionality in jlrs can use strings instead. They're also used
35/// as the building-block of expressions.
36///
37/// One special property of `Symbol`s is that they're never freed by the garbage collector after
38/// they've been created.
39#[repr(transparent)]
40#[derive(Copy, Clone, PartialEq, Eq)]
41pub struct Symbol<'scope>(NonNull<jl_sym_t>, PhantomData<&'scope ()>);
42
43impl<'scope> Symbol<'scope> {
44    /// Convert the given string to a `Symbol`.
45    #[inline]
46    pub fn new<S, Tgt>(_: &Tgt, symbol: S) -> Self
47    where
48        S: AsRef<str>,
49        Tgt: Target<'scope>,
50    {
51        let bytes = symbol.as_ref().as_bytes();
52
53        if let Some(sym) = CACHE.find(bytes) {
54            return sym;
55        }
56
57        // Safety: Can only be called from a thread known to Julia, symbols are globally rooted
58        unsafe {
59            let sym = jl_symbol_n(bytes.as_ptr().cast(), bytes.len());
60            let sym = Symbol::wrap_non_null(NonNull::new_unchecked(sym), Private);
61            CACHE.insert(bytes.into(), sym);
62            sym
63        }
64    }
65
66    /// Convert the given byte slice to a `Symbol`.
67    pub fn new_bytes<N, Tgt>(target: Tgt, symbol: N) -> TargetException<'scope, 'static, Self, Tgt>
68    where
69        N: AsRef<[u8]>,
70        Tgt: Target<'scope>,
71    {
72        let bytes = symbol.as_ref();
73
74        if let Some(sym) = CACHE.find(bytes) {
75            unsafe {
76                return target.exception_from_ptr(Ok(sym), Private);
77            }
78        }
79
80        unsafe {
81            let callback = || jl_symbol_n(bytes.as_ptr().cast(), bytes.len());
82
83            match catch_exceptions(callback, unwrap_exc) {
84                Ok(sym) => {
85                    let sym = Symbol::wrap_non_null(NonNull::new_unchecked(sym), Private);
86                    CACHE.insert(bytes.into(), sym);
87                    Ok(sym)
88                }
89                Err(e) => target.exception_from_ptr(Err(e), Private),
90            }
91        }
92    }
93
94    /// Convert the given byte slice to a `Symbol`.
95    ///
96    /// Safety: if `symbol` contains `0`, an error is thrown which is not caught.
97    #[inline]
98    pub unsafe fn new_bytes_unchecked<S, Tgt>(_: &Tgt, symbol: S) -> Self
99    where
100        S: AsRef<[u8]>,
101        Tgt: Target<'scope>,
102    {
103        let bytes = symbol.as_ref();
104
105        if let Some(sym) = CACHE.find(bytes) {
106            return sym;
107        }
108
109        // Safety: Can only be called from a thread known to Julia, symbols are globally rooted
110        unsafe {
111            let sym = jl_symbol_n(bytes.as_ptr().cast(), bytes.len());
112            let sym = Symbol::wrap_non_null(NonNull::new_unchecked(sym), Private);
113            CACHE.insert(bytes.into(), sym);
114            sym
115        }
116    }
117
118    /// Generate a new unique `Symbol`.
119    #[inline]
120    pub fn generate<Tgt>(_: &Tgt) -> Self
121    where
122        Tgt: Target<'scope>,
123    {
124        unsafe {
125            let sym = jl_gensym();
126            Symbol::wrap_non_null(NonNull::new_unchecked(sym), Private)
127        }
128    }
129
130    /// Generate a new unique tagged `Symbol`.
131    #[inline]
132    pub fn generate_tagged<S, Tgt>(_: &Tgt, tag: S) -> Self
133    where
134        S: AsRef<str>,
135        Tgt: Target<'scope>,
136    {
137        unsafe {
138            let tag = tag.as_ref().as_bytes();
139            let sym = jl_tagged_gensym(tag.as_ptr() as _, tag.len());
140            Symbol::wrap_non_null(NonNull::new_unchecked(sym), Private)
141        }
142    }
143
144    /// Extend the `Symbol`'s lifetime. A `Symbol` is never freed by the garbage collector, its
145    /// lifetime can be safely extended.
146    ///
147    /// [`Value`]: crate::data::managed::value::Value
148    #[inline]
149    pub fn extend<'target, Tgt>(self, _: &Tgt) -> Symbol<'target>
150    where
151        Tgt: Target<'target>,
152    {
153        // Safety: symbols are globally rooted
154        unsafe { Symbol::wrap_non_null(self.unwrap_non_null(Private), Private) }
155    }
156
157    /// Convert `self` to a `String`.
158    #[inline]
159    pub fn as_string(self) -> JlrsResult<String> {
160        self.as_str().map(Into::into)
161    }
162
163    /// View `self` as a string slice. Returns an error if the symbol is not valid UTF8.
164    #[inline]
165    pub fn as_str(self) -> JlrsResult<&'scope str> {
166        // Safety: symbols are globally rooted
167        unsafe {
168            let ptr = jlrs_symbol_name(self.unwrap(Private)).cast();
169            let symbol = CStr::from_ptr(ptr);
170            Ok(symbol.to_str().map_err(JlrsError::other)?)
171        }
172    }
173
174    /// View `self` as a `Cstr`.
175    #[inline]
176    pub fn as_cstr(self) -> &'scope CStr {
177        // Safety: symbols are globally rooted
178        unsafe {
179            let ptr = jlrs_symbol_name(self.unwrap(Private));
180            &CStr::from_ptr(ptr.cast())
181        }
182    }
183
184    /// View `self` as an slice of bytes without the trailing null.
185    #[inline]
186    pub fn as_bytes(self) -> &'scope [u8] {
187        // Safety: symbols are globally rooted
188        unsafe {
189            let ptr = jlrs_symbol_name(self.unwrap(Private)).cast();
190            let symbol = CStr::from_ptr(ptr);
191            symbol.to_bytes()
192        }
193    }
194
195    fn hash(self) -> usize {
196        unsafe { jlrs_symbol_hash(self.unwrap(Private)) }
197    }
198}
199
200impl Hash for Symbol<'_> {
201    #[inline]
202    fn hash<H: Hasher>(&self, state: &mut H) {
203        state.write_usize((*self).hash())
204    }
205}
206
207impl<S: StaticSymbol> PartialEq<S> for Symbol<'_> {
208    fn eq(&self, _: &S) -> bool {
209        unsafe {
210            let unrooted = Unrooted::new();
211            let other = S::get_symbol(&unrooted);
212            self.0 == other.0 && self.1 == other.1
213        }
214    }
215}
216
217impl<S: StaticSymbol> PartialEq<Sym<'_, S>> for Symbol<'_> {
218    fn eq(&self, _: &Sym<S>) -> bool {
219        unsafe {
220            let unrooted = Unrooted::new();
221            let other = S::get_symbol(&unrooted);
222            self.0 == other.0 && self.1 == other.1
223        }
224    }
225}
226
227impl<S: StaticSymbol> PartialEq<Sym<'_, PhantomData<S>>> for Symbol<'_> {
228    fn eq(&self, _: &Sym<PhantomData<S>>) -> bool {
229        unsafe {
230            let unrooted = Unrooted::new();
231            let other = S::get_symbol(&unrooted);
232            self.0 == other.0 && self.1 == other.1
233        }
234    }
235}
236
237impl_julia_typecheck!(Symbol<'scope>, jl_symbol_type, 'scope);
238impl_debug!(Symbol<'_>);
239
240impl<'scope> ManagedPriv<'scope, '_> for Symbol<'scope> {
241    type Wraps = jl_sym_t;
242    type WithLifetimes<'target, 'da> = Symbol<'target>;
243    const NAME: &'static str = "Symbol";
244
245    // Safety: `inner` must not have been freed yet, the result must never be
246    // used after the GC might have freed it.
247    #[inline]
248    unsafe fn wrap_non_null(inner: NonNull<Self::Wraps>, _: Private) -> Self {
249        Self(inner, PhantomData)
250    }
251
252    #[inline]
253    fn unwrap_non_null(self, _: Private) -> NonNull<Self::Wraps> {
254        self.0
255    }
256}
257
258impl_construct_type_managed!(Symbol, 1, jl_symbol_type);
259
260/// A [`Symbol`] that has not been explicitly rooted.
261pub type WeakSymbol<'scope> = Weak<'scope, 'static, Symbol<'scope>>;
262
263/// A [`WeakSymbol`] with static lifetimes. This is a useful shorthand for signatures of
264/// `ccall`able functions that return a [`Symbol`].
265pub type SymbolRet = WeakSymbol<'static>;
266
267impl_valid_layout!(WeakSymbol, Symbol, jl_symbol_type);
268
269use crate::memory::target::TargetType;
270
271/// `Task` or `WeakTask`, depending on the target type `Tgt`.
272pub type SymbolData<'target, Tgt> = <Tgt as TargetType<'target>>::Data<'static, Symbol<'target>>;
273
274/// `JuliaResult<Symbol>` or `WeakJuliaResult<WeakSymbol>`, depending on the target type `Tgt`.
275pub type SymbolResult<'target, Tgt> = TargetResult<'target, 'static, Symbol<'target>, Tgt>;
276
277pub type SymbolUnbound = Symbol<'static>;
278
279impl_ccall_arg_managed!(Symbol, 1);
280impl_into_typed!(Symbol);
281
282struct SymbolCache {
283    inner: Cache<FxHashMap<Vec<u8>, Symbol<'static>>>,
284}
285
286impl SymbolCache {
287    const fn new() -> Self {
288        let map = std::collections::HashMap::with_hasher(FxBuildHasher);
289        let inner = Cache::new(map);
290
291        SymbolCache { inner }
292    }
293
294    #[inline]
295    fn find(&self, key: &[u8]) -> Option<Symbol<'static>> {
296        unsafe {
297            self.inner.read(
298                #[inline]
299                |cache| cache.cache().get(key).copied(),
300            )
301        }
302    }
303
304    #[inline]
305    fn insert(&self, key: Vec<u8>, sym: Symbol) {
306        unsafe {
307            self.inner.write(
308                #[inline]
309                |cache| cache.cache_mut().insert(key, erase_scope_lifetime(sym)),
310            )
311        };
312    }
313}