godot_core/builtin/string/
string_name.rs

1/*
2 * Copyright (c) godot-rust; Bromeon and contributors.
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
6 */
7
8use std::fmt;
9
10use godot_ffi as sys;
11use sys::{ffi_methods, ExtVariantType, GodotFfi};
12
13use crate::builtin::{inner, Encoding, GString, NodePath, Variant};
14use crate::meta::error::StringError;
15use crate::meta::AsArg;
16use crate::{impl_shared_string_api, meta};
17
18/// A string optimized for unique names.
19///
20/// StringNames are immutable strings designed for representing unique names. StringName ensures that only
21/// one instance of a given name exists.
22///
23/// # Ordering
24///
25/// In Godot, `StringName`s are **not** ordered lexicographically, and the ordering relation is **not** stable across multiple runs of your
26/// application. Therefore, this type does not implement `PartialOrd` and `Ord`, as it would be very easy to introduce bugs by accidentally
27/// relying on lexicographical ordering.
28///
29/// Instead, we provide [`transient_ord()`][Self::transient_ord] for ordering relations.
30///
31/// # Null bytes
32///
33/// Note that Godot ignores any bytes after a null-byte. This means that for instance `"hello, world!"` and  \
34/// `"hello, world!\0 ignored by Godot"` will be treated as the same string if converted to a `StringName`.
35///
36/// # All string types
37///
38/// | Intended use case | String type                                |
39/// |-------------------|--------------------------------------------|
40/// | General purpose   | [`GString`][crate::builtin::GString]       |
41/// | Interned names    | **`StringName`**                           |
42/// | Scene-node paths  | [`NodePath`][crate::builtin::NodePath]     |
43///
44/// # Godot docs
45///
46/// [`StringName` (stable)](https://docs.godotengine.org/en/stable/classes/class_stringname.html)
47// Currently we rely on `transparent` for `borrow_string_sys`.
48#[repr(transparent)]
49pub struct StringName {
50    opaque: sys::types::OpaqueStringName,
51}
52
53impl StringName {
54    fn from_opaque(opaque: sys::types::OpaqueStringName) -> Self {
55        Self { opaque }
56    }
57
58    /// Convert string from bytes with given encoding, returning `Err` on validation errors.
59    ///
60    /// Intermediate `NUL` characters are not accepted in Godot and always return `Err`.
61    ///
62    /// Some notes on the encodings:
63    /// - **Latin-1:** Since every byte is a valid Latin-1 character, no validation besides the `NUL` byte is performed.
64    ///   It is your responsibility to ensure that the input is meaningful under Latin-1.
65    /// - **ASCII**: Subset of Latin-1, which is additionally validated to be valid, non-`NUL` ASCII characters.
66    /// - **UTF-8**: The input is validated to be UTF-8.
67    ///
68    /// Specifying incorrect encoding is safe, but may result in unintended string values.
69    pub fn try_from_bytes(bytes: &[u8], encoding: Encoding) -> Result<Self, StringError> {
70        Self::try_from_bytes_with_nul_check(bytes, encoding, true)
71    }
72
73    /// Convert string from bytes with given encoding, returning `Err` on validation errors.
74    ///
75    /// Convenience function for [`try_from_bytes()`](Self::try_from_bytes); see its docs for more information.
76    ///
77    /// When called with `Encoding::Latin1`, this can be slightly more efficient than `try_from_bytes()`.
78    pub fn try_from_cstr(cstr: &std::ffi::CStr, encoding: Encoding) -> Result<Self, StringError> {
79        // Since Godot 4.2, we can directly short-circuit for Latin-1, which takes a null-terminated C string.
80        if encoding == Encoding::Latin1 {
81            // Note: CStr guarantees no intermediate NUL bytes, so we don't need to check for them.
82
83            let is_static = sys::conv::SYS_FALSE;
84            let s = unsafe {
85                Self::new_with_string_uninit(|string_ptr| {
86                    let ctor = sys::interface_fn!(string_name_new_with_latin1_chars);
87                    ctor(
88                        string_ptr,
89                        cstr.as_ptr() as *const std::ffi::c_char,
90                        is_static,
91                    );
92                })
93            };
94            return Ok(s);
95        }
96
97        Self::try_from_bytes_with_nul_check(cstr.to_bytes(), encoding, false)
98    }
99
100    fn try_from_bytes_with_nul_check(
101        bytes: &[u8],
102        encoding: Encoding,
103        check_nul: bool,
104    ) -> Result<Self, StringError> {
105        match encoding {
106            Encoding::Ascii => {
107                // ASCII is a subset of UTF-8, and UTF-8 has a more direct implementation than Latin-1; thus use UTF-8 via `From<&str>`.
108                if !bytes.is_ascii() {
109                    Err(StringError::new("invalid ASCII"))
110                } else if check_nul && bytes.contains(&0) {
111                    Err(StringError::new("intermediate NUL byte in ASCII string"))
112                } else {
113                    // SAFETY: ASCII is a subset of UTF-8 and was verified above.
114                    let ascii = unsafe { std::str::from_utf8_unchecked(bytes) };
115                    Ok(Self::from(ascii))
116                }
117            }
118            Encoding::Latin1 => {
119                // This branch is short-circuited if invoked for CStr, which uses `string_name_new_with_latin1_chars`
120                // (requires nul-termination). In general, fall back to GString conversion.
121                GString::try_from_bytes_with_nul_check(bytes, Encoding::Latin1, check_nul)
122                    .map(|s| Self::from(&s))
123            }
124            Encoding::Utf8 => {
125                // from_utf8() also checks for intermediate NUL bytes.
126                let utf8 = std::str::from_utf8(bytes);
127
128                utf8.map(StringName::from)
129                    .map_err(|e| StringError::with_source("invalid UTF-8", e))
130            }
131        }
132    }
133
134    /// Number of characters in the string.
135    ///
136    /// _Godot equivalent: `length`_
137    #[doc(alias = "length")]
138    pub fn len(&self) -> usize {
139        self.as_inner().length() as usize
140    }
141
142    crate::declare_hash_u32_method! {
143        /// Returns a 32-bit integer hash value representing the string.
144    }
145
146    #[deprecated = "renamed to hash_u32"]
147    pub fn hash(&self) -> u32 {
148        self.as_inner()
149            .hash()
150            .try_into()
151            .expect("Godot hashes are uint32_t")
152    }
153
154    meta::declare_arg_method! {
155        /// Use as argument for an [`impl AsArg<GString|NodePath>`][crate::meta::AsArg] parameter.
156        ///
157        /// This is a convenient way to convert arguments of similar string types.
158        ///
159        /// # Example
160        /// [`Node::set_name()`][crate::classes::Node::set_name] takes `GString`, let's pass a `StringName`:
161        /// ```no_run
162        /// # use godot::prelude::*;
163        /// let needle = StringName::from("str");
164        /// let haystack = GString::from("a long string");
165        /// let found = haystack.find(needle.arg());
166        /// ```
167    }
168
169    /// O(1), non-lexicographic, non-stable ordering relation.
170    ///
171    /// The result of the comparison is **not** lexicographic and **not** stable across multiple runs of your application.
172    ///
173    /// However, it is very fast. It doesn't depend on the length of the strings, but on the memory location of string names.
174    /// This can still be useful if you need to establish an ordering relation, but are not interested in the actual order of the strings
175    /// (example: binary search).
176    ///
177    /// For lexicographical ordering, convert to `GString` (significantly slower).
178    pub fn transient_ord(&self) -> TransientStringNameOrd<'_> {
179        TransientStringNameOrd(self)
180    }
181
182    ffi_methods! {
183        type sys::GDExtensionStringNamePtr = *mut Opaque;
184
185        // Note: unlike from_sys, from_string_sys does not default-construct instance first. Typical usage in C++ is placement new.
186        fn new_from_string_sys = new_from_sys;
187        fn new_with_string_uninit = new_with_uninit;
188        fn string_sys = sys;
189        fn string_sys_mut = sys_mut;
190    }
191
192    /// Consumes self and turns it into a sys-ptr, should be used together with [`from_owned_string_sys`](Self::from_owned_string_sys).
193    ///
194    /// This will leak memory unless `from_owned_string_sys` is called on the returned pointer.
195    pub(crate) fn into_owned_string_sys(self) -> sys::GDExtensionStringNamePtr {
196        sys::static_assert_eq_size_align!(StringName, sys::types::OpaqueStringName);
197
198        let leaked = Box::into_raw(Box::new(self));
199        leaked.cast()
200    }
201
202    /// Creates a `StringName` from a sys-ptr without incrementing the refcount.
203    ///
204    /// # Safety
205    ///
206    /// * Must only be used on a pointer returned from a call to [`into_owned_string_sys`](Self::into_owned_string_sys).
207    /// * Must not be called more than once on the same pointer.
208    #[deny(unsafe_op_in_unsafe_fn)]
209    pub(crate) unsafe fn from_owned_string_sys(ptr: sys::GDExtensionStringNamePtr) -> Self {
210        sys::static_assert_eq_size_align!(StringName, sys::types::OpaqueStringName);
211
212        let ptr = ptr.cast::<Self>();
213
214        // SAFETY: `ptr` was returned from a call to `into_owned_string_sys`, which means it was created by a call to
215        // `Box::into_raw`, thus we can use `Box::from_raw` here. Additionally, this is only called once on this pointer.
216        let boxed = unsafe { Box::from_raw(ptr) };
217        *boxed
218    }
219
220    /// Convert a `StringName` sys pointer to a reference with unbounded lifetime.
221    ///
222    /// # Safety
223    ///
224    /// `ptr` must point to a live `StringName` for the duration of `'a`.
225    pub(crate) unsafe fn borrow_string_sys<'a>(
226        ptr: sys::GDExtensionConstStringNamePtr,
227    ) -> &'a StringName {
228        sys::static_assert_eq_size_align!(StringName, sys::types::OpaqueStringName);
229        &*(ptr.cast::<StringName>())
230    }
231
232    /// Convert a `StringName` sys pointer to a mutable reference with unbounded lifetime.
233    ///
234    /// # Safety
235    ///
236    /// - `ptr` must point to a live `StringName` for the duration of `'a`.
237    /// - Must be exclusive - no other reference to given `StringName` instance can exist for the duration of `'a`.
238    pub(crate) unsafe fn borrow_string_sys_mut<'a>(
239        ptr: sys::GDExtensionStringNamePtr,
240    ) -> &'a mut StringName {
241        sys::static_assert_eq_size_align!(StringName, sys::types::OpaqueStringName);
242        &mut *(ptr.cast::<StringName>())
243    }
244
245    #[doc(hidden)]
246    pub fn as_inner(&self) -> inner::InnerStringName<'_> {
247        inner::InnerStringName::from_outer(self)
248    }
249
250    #[doc(hidden)] // Private for now. Needs API discussion, also regarding overlap with try_from_cstr().
251    pub fn __cstr(c_str: &'static std::ffi::CStr) -> Self {
252        // This used to be set to true, but `p_is_static` parameter in Godot should only be enabled if the result is indeed stored
253        // in a static. See discussion in https://github.com/godot-rust/gdext/pull/1316. We may unify this into a regular constructor,
254        // or provide a dedicated StringName cache (similar to ClassId cache) in the future, which would be freed on shutdown.
255        let is_static = false;
256
257        Self::__cstr_with_static(c_str, is_static)
258    }
259
260    /// Creates a `StringName` from a static ASCII/Latin-1 `c"string"`.
261    ///
262    /// If `is_static` is true, avoids unnecessary copies and allocations and directly uses the backing buffer. However, this must
263    /// be stored in an actual `static` to not cause leaks/error messages with Godot. For literals, use `is_static=false`.
264    ///
265    /// Note that while Latin-1 encoding is the most common encoding for c-strings, it isn't a requirement. So if your c-string
266    /// uses a different encoding (e.g. UTF-8), it is possible that some characters will not show up as expected.
267    ///
268    /// # Safety
269    /// `c_str` must be a static c-string that remains valid for the entire program duration.
270    ///
271    /// # Example
272    /// ```no_run
273    /// use godot::builtin::StringName;
274    ///
275    /// // '±' is a Latin-1 character with codepoint 0xB1. Note that this is not UTF-8, where it would need two bytes.
276    /// let sname = StringName::__cstr(c"\xb1 Latin-1 string");
277    /// ```
278    #[doc(hidden)] // Private for now. Needs API discussion, also regarding overlap with try_from_cstr().
279    pub fn __cstr_with_static(c_str: &'static std::ffi::CStr, is_static: bool) -> Self {
280        // SAFETY: c_str is nul-terminated and remains valid for entire program duration.
281        unsafe {
282            Self::new_with_string_uninit(|ptr| {
283                sys::interface_fn!(string_name_new_with_latin1_chars)(
284                    ptr,
285                    c_str.as_ptr(),
286                    sys::conv::bool_to_sys(is_static),
287                )
288            })
289        }
290    }
291}
292
293// SAFETY:
294// - `move_return_ptr`
295//   Nothing special needs to be done beyond a `std::mem::swap` when returning a StringName.
296//   So we can just use `ffi_methods`.
297//
298// - `from_arg_ptr`
299//   StringNames are properly initialized through a `from_sys` call, but the ref-count should be
300//   incremented as that is the callee's responsibility. Which we do by calling
301//   `std::mem::forget(string_name.clone())`.
302unsafe impl GodotFfi for StringName {
303    const VARIANT_TYPE: ExtVariantType = ExtVariantType::Concrete(sys::VariantType::STRING_NAME);
304
305    ffi_methods! { type sys::GDExtensionTypePtr = *mut Opaque; .. }
306}
307
308meta::impl_godot_as_self!(StringName: ByRef);
309
310impl_builtin_traits! {
311    for StringName {
312        Default => string_name_construct_default;
313        Clone => string_name_construct_copy;
314        Drop => string_name_destroy;
315        Eq => string_name_operator_equal;
316        // Do not provide PartialOrd or Ord. Even though Godot provides a `operator <`, it is non-lexicographic and non-deterministic
317        // (based on pointers). See transient_ord() method.
318        Hash;
319    }
320}
321
322impl_shared_string_api! {
323    builtin: StringName,
324    find_builder: ExStringNameFind,
325    split_builder: ExStringNameSplit,
326}
327
328impl fmt::Display for StringName {
329    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
330        let s = GString::from(self);
331        <GString as fmt::Display>::fmt(&s, f)
332    }
333}
334
335/// Uses literal syntax from GDScript: `&"string_name"`
336impl fmt::Debug for StringName {
337    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
338        let string = GString::from(self);
339        write!(f, "&\"{string}\"")
340    }
341}
342
343// SAFETY: StringName is immutable once constructed. Shared references can thus not undergo mutation.
344unsafe impl Sync for StringName {}
345
346// SAFETY: StringName is immutable once constructed. Also, its inc-ref/dec-ref operations are mutex-protected in Godot.
347// That is, it's safe to construct a StringName on thread A and destroy it on thread B.
348unsafe impl Send for StringName {}
349
350// ----------------------------------------------------------------------------------------------------------------------------------------------
351// Conversion from/into other string-types
352
353impl_rust_string_conv!(StringName);
354
355impl From<&str> for StringName {
356    fn from(string: &str) -> Self {
357        let utf8 = string.as_bytes();
358
359        // SAFETY: Rust guarantees validity and range of string.
360        unsafe {
361            Self::new_with_string_uninit(|ptr| {
362                sys::interface_fn!(string_name_new_with_utf8_chars_and_len)(
363                    ptr,
364                    utf8.as_ptr() as *const std::ffi::c_char,
365                    utf8.len() as i64,
366                );
367            })
368        }
369    }
370}
371
372impl From<&String> for StringName {
373    fn from(value: &String) -> Self {
374        value.as_str().into()
375    }
376}
377
378impl From<&GString> for StringName {
379    /// See also [`GString::to_string_name()`].
380    fn from(string: &GString) -> Self {
381        unsafe {
382            Self::new_with_uninit(|self_ptr| {
383                let ctor = sys::builtin_fn!(string_name_from_string);
384                let args = [string.sys()];
385                ctor(self_ptr, args.as_ptr());
386            })
387        }
388    }
389}
390
391impl From<&NodePath> for StringName {
392    fn from(path: &NodePath) -> Self {
393        Self::from(&GString::from(path))
394    }
395}
396
397// ----------------------------------------------------------------------------------------------------------------------------------------------
398// Ordering
399
400/// Type that implements `Ord` for `StringNames`.
401///
402/// See [`StringName::transient_ord()`].
403pub struct TransientStringNameOrd<'a>(&'a StringName);
404
405impl PartialEq for TransientStringNameOrd<'_> {
406    fn eq(&self, other: &Self) -> bool {
407        self.0 == other.0
408    }
409}
410
411impl Eq for TransientStringNameOrd<'_> {}
412
413impl PartialOrd for TransientStringNameOrd<'_> {
414    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
415        Some(self.cmp(other))
416    }
417}
418
419impl Ord for TransientStringNameOrd<'_> {
420    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
421        // SAFETY: builtin operator provided by Godot.
422        let op_less = |lhs, rhs| unsafe {
423            let mut result = false;
424            sys::builtin_call! {
425                string_name_operator_less(lhs, rhs, result.sys_mut())
426            }
427            result
428        };
429
430        let self_ptr = self.0.sys();
431        let other_ptr = other.0.sys();
432
433        if op_less(self_ptr, other_ptr) {
434            std::cmp::Ordering::Less
435        } else if op_less(other_ptr, self_ptr) {
436            std::cmp::Ordering::Greater
437        } else if self.eq(other) {
438            std::cmp::Ordering::Equal
439        } else {
440            panic!(
441                "Godot provides inconsistent StringName ordering for \"{}\" and \"{}\"",
442                self.0, other.0
443            );
444        }
445    }
446}
447
448// ----------------------------------------------------------------------------------------------------------------------------------------------
449// serde support
450
451#[cfg(feature = "serde")] #[cfg_attr(published_docs, doc(cfg(feature = "serde")))]
452mod serialize {
453    use std::fmt::Formatter;
454
455    use serde::de::{Error, Visitor};
456    use serde::{Deserialize, Deserializer, Serialize, Serializer};
457
458    use super::*;
459
460    // For "Available on crate feature `serde`" in docs. Cannot be inherited from module. Also does not support #[derive] (e.g. in Vector2).
461    #[cfg_attr(published_docs, doc(cfg(feature = "serde")))]
462    impl Serialize for StringName {
463        #[inline]
464        fn serialize<S>(
465            &self,
466            serializer: S,
467        ) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
468        where
469            S: Serializer,
470        {
471            serializer.serialize_str(&self.to_string())
472        }
473    }
474
475    #[cfg_attr(published_docs, doc(cfg(feature = "serde")))]
476    impl<'de> Deserialize<'de> for StringName {
477        #[inline]
478        fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
479        where
480            D: Deserializer<'de>,
481        {
482            struct StringNameVisitor;
483            impl Visitor<'_> for StringNameVisitor {
484                type Value = StringName;
485
486                fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
487                    formatter.write_str("a StringName")
488                }
489
490                fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
491                where
492                    E: Error,
493                {
494                    Ok(StringName::from(s))
495                }
496            }
497
498            deserializer.deserialize_str(StringNameVisitor)
499        }
500    }
501}
502
503// TODO(v0.4.x): consider re-exposing in public API. Open questions: thread-safety, performance, memory leaks, global overhead.
504// Possibly in a more general StringName cache, similar to ClassId. See https://github.com/godot-rust/gdext/pull/1316.
505/// Creates and gets a reference to a static `StringName` from a ASCII/Latin-1 `c"string"`.
506///
507/// This is the fastest way to create a StringName repeatedly, with the result being cached and never released, like `SNAME` in Godot source code. Suitable for scenarios where high performance is required.
508#[macro_export]
509macro_rules! static_sname {
510    ($str:literal) => {{
511        use std::sync::OnceLock;
512
513        let c_str: &'static std::ffi::CStr = $str;
514        static SNAME: OnceLock<StringName> = OnceLock::new();
515        SNAME.get_or_init(|| StringName::__cstr_with_static(c_str, true))
516    }};
517}