Skip to main content

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