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 /// Gets the UTF-32 character slice from a `StringName`.
183 ///
184 /// # Compatibility
185 /// This method is only available for Godot 4.5 and later, where `StringName` to `GString` conversions preserve the
186 /// underlying buffer pointer via reference counting.
187 #[cfg(since_api = "4.5")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.5")))]
188 pub fn chars(&self) -> &[char] {
189 let gstring = GString::from(self);
190 let (ptr, len) = gstring.raw_slice();
191
192 // Even when len == 0, from_raw_parts requires ptr != null.
193 if ptr.is_null() {
194 return &[];
195 }
196
197 // SAFETY: In Godot 4.5+, StringName always uses String (GString) as backing storage internally, see
198 // https://github.com/godotengine/godot/pull/104985.
199 // The conversion preserves the original buffer pointer via reference counting. As long as the GString is not modified,
200 // the buffer remains valid and is kept alive by the StringName's reference count, even after the temporary GString drops.
201 // The returned slice's lifetime is tied to &self, which is correct since self keeps the buffer alive.
202 unsafe { std::slice::from_raw_parts(ptr, len) }
203 }
204
205 ffi_methods! {
206 type sys::GDExtensionStringNamePtr = *mut Opaque;
207
208 // Note: unlike from_sys, from_string_sys does not default-construct instance first. Typical usage in C++ is placement new.
209 fn new_from_string_sys = new_from_sys;
210 fn new_with_string_uninit = new_with_uninit;
211 fn string_sys = sys;
212 fn string_sys_mut = sys_mut;
213 }
214
215 /// Consumes self and turns it into a sys-ptr, should be used together with [`from_owned_string_sys`](Self::from_owned_string_sys).
216 ///
217 /// This will leak memory unless `from_owned_string_sys` is called on the returned pointer.
218 pub(crate) fn into_owned_string_sys(self) -> sys::GDExtensionStringNamePtr {
219 sys::static_assert_eq_size_align!(StringName, sys::types::OpaqueStringName);
220
221 let leaked = Box::into_raw(Box::new(self));
222 leaked.cast()
223 }
224
225 /// Creates a `StringName` from a sys-ptr without incrementing the refcount.
226 ///
227 /// # Safety
228 ///
229 /// * Must only be used on a pointer returned from a call to [`into_owned_string_sys`](Self::into_owned_string_sys).
230 /// * Must not be called more than once on the same pointer.
231 #[deny(unsafe_op_in_unsafe_fn)]
232 pub(crate) unsafe fn from_owned_string_sys(ptr: sys::GDExtensionStringNamePtr) -> Self {
233 sys::static_assert_eq_size_align!(StringName, sys::types::OpaqueStringName);
234
235 let ptr = ptr.cast::<Self>();
236
237 // SAFETY: `ptr` was returned from a call to `into_owned_string_sys`, which means it was created by a call to
238 // `Box::into_raw`, thus we can use `Box::from_raw` here. Additionally, this is only called once on this pointer.
239 let boxed = unsafe { Box::from_raw(ptr) };
240 *boxed
241 }
242
243 /// Convert a `StringName` sys pointer to a reference with unbounded lifetime.
244 ///
245 /// # Safety
246 ///
247 /// `ptr` must point to a live `StringName` for the duration of `'a`.
248 pub(crate) unsafe fn borrow_string_sys<'a>(
249 ptr: sys::GDExtensionConstStringNamePtr,
250 ) -> &'a StringName {
251 sys::static_assert_eq_size_align!(StringName, sys::types::OpaqueStringName);
252 &*(ptr.cast::<StringName>())
253 }
254
255 /// Convert a `StringName` sys pointer to a mutable reference with unbounded lifetime.
256 ///
257 /// # Safety
258 ///
259 /// - `ptr` must point to a live `StringName` for the duration of `'a`.
260 /// - Must be exclusive - no other reference to given `StringName` instance can exist for the duration of `'a`.
261 pub(crate) unsafe fn borrow_string_sys_mut<'a>(
262 ptr: sys::GDExtensionStringNamePtr,
263 ) -> &'a mut StringName {
264 sys::static_assert_eq_size_align!(StringName, sys::types::OpaqueStringName);
265 &mut *(ptr.cast::<StringName>())
266 }
267
268 #[doc(hidden)]
269 pub fn as_inner(&self) -> inner::InnerStringName<'_> {
270 inner::InnerStringName::from_outer(self)
271 }
272
273 #[doc(hidden)] // Private for now. Needs API discussion, also regarding overlap with try_from_cstr().
274 pub fn __cstr(c_str: &'static std::ffi::CStr) -> Self {
275 // This used to be set to true, but `p_is_static` parameter in Godot should only be enabled if the result is indeed stored
276 // in a static. See discussion in https://github.com/godot-rust/gdext/pull/1316. We may unify this into a regular constructor,
277 // or provide a dedicated StringName cache (similar to ClassId cache) in the future, which would be freed on shutdown.
278 let is_static = false;
279
280 Self::__cstr_with_static(c_str, is_static)
281 }
282
283 /// Creates a `StringName` from a static ASCII/Latin-1 `c"string"`.
284 ///
285 /// If `is_static` is true, avoids unnecessary copies and allocations and directly uses the backing buffer. However, this must
286 /// be stored in an actual `static` to not cause leaks/error messages with Godot. For literals, use `is_static=false`.
287 ///
288 /// Note that while Latin-1 encoding is the most common encoding for c-strings, it isn't a requirement. So if your c-string
289 /// uses a different encoding (e.g. UTF-8), it is possible that some characters will not show up as expected.
290 ///
291 /// # Safety
292 /// `c_str` must be a static c-string that remains valid for the entire program duration.
293 ///
294 /// # Example
295 /// ```no_run
296 /// use godot::builtin::StringName;
297 ///
298 /// // '±' is a Latin-1 character with codepoint 0xB1. Note that this is not UTF-8, where it would need two bytes.
299 /// let sname = StringName::__cstr(c"\xb1 Latin-1 string");
300 /// ```
301 #[doc(hidden)] // Private for now. Needs API discussion, also regarding overlap with try_from_cstr().
302 pub fn __cstr_with_static(c_str: &'static std::ffi::CStr, is_static: bool) -> Self {
303 // SAFETY: c_str is nul-terminated and remains valid for entire program duration.
304 unsafe {
305 Self::new_with_string_uninit(|ptr| {
306 sys::interface_fn!(string_name_new_with_latin1_chars)(
307 ptr,
308 c_str.as_ptr(),
309 sys::conv::bool_to_sys(is_static),
310 )
311 })
312 }
313 }
314}
315
316// SAFETY:
317// - `move_return_ptr`
318// Nothing special needs to be done beyond a `std::mem::swap` when returning a StringName.
319// So we can just use `ffi_methods`.
320//
321// - `from_arg_ptr`
322// StringNames are properly initialized through a `from_sys` call, but the ref-count should be
323// incremented as that is the callee's responsibility. Which we do by calling
324// `std::mem::forget(string_name.clone())`.
325unsafe impl GodotFfi for StringName {
326 const VARIANT_TYPE: ExtVariantType = ExtVariantType::Concrete(sys::VariantType::STRING_NAME);
327
328 ffi_methods! { type sys::GDExtensionTypePtr = *mut Opaque; .. }
329}
330
331meta::impl_godot_as_self!(StringName: ByRef);
332
333impl_builtin_traits! {
334 for StringName {
335 Default => string_name_construct_default;
336 Clone => string_name_construct_copy;
337 Drop => string_name_destroy;
338 Eq => string_name_operator_equal;
339 // Do not provide PartialOrd or Ord. Even though Godot provides a `operator <`, it is non-lexicographic and non-deterministic
340 // (based on pointers). See transient_ord() method.
341 Hash;
342 }
343}
344
345impl_shared_string_api! {
346 builtin: StringName,
347 find_builder: ExStringNameFind,
348 split_builder: ExStringNameSplit,
349}
350
351impl fmt::Display for StringName {
352 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
353 let s = GString::from(self);
354 <GString as fmt::Display>::fmt(&s, f)
355 }
356}
357
358/// Uses literal syntax from GDScript: `&"string_name"`
359impl fmt::Debug for StringName {
360 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
361 let string = GString::from(self);
362 write!(f, "&\"{string}\"")
363 }
364}
365
366// SAFETY: StringName is immutable once constructed. Shared references can thus not undergo mutation.
367unsafe impl Sync for StringName {}
368
369// SAFETY: StringName is immutable once constructed. Also, its inc-ref/dec-ref operations are mutex-protected in Godot.
370// That is, it's safe to construct a StringName on thread A and destroy it on thread B.
371unsafe impl Send for StringName {}
372
373// ----------------------------------------------------------------------------------------------------------------------------------------------
374// Conversion from/into other string-types
375
376impl_rust_string_conv!(StringName);
377
378impl From<&str> for StringName {
379 fn from(string: &str) -> Self {
380 let utf8 = string.as_bytes();
381
382 // SAFETY: Rust guarantees validity and range of string.
383 unsafe {
384 Self::new_with_string_uninit(|ptr| {
385 sys::interface_fn!(string_name_new_with_utf8_chars_and_len)(
386 ptr,
387 utf8.as_ptr() as *const std::ffi::c_char,
388 utf8.len() as i64,
389 );
390 })
391 }
392 }
393}
394
395impl From<&String> for StringName {
396 fn from(value: &String) -> Self {
397 value.as_str().into()
398 }
399}
400
401impl From<&GString> for StringName {
402 /// See also [`GString::to_string_name()`].
403 fn from(string: &GString) -> Self {
404 unsafe {
405 Self::new_with_uninit(|self_ptr| {
406 let ctor = sys::builtin_fn!(string_name_from_string);
407 let args = [string.sys()];
408 ctor(self_ptr, args.as_ptr());
409 })
410 }
411 }
412}
413
414impl From<&NodePath> for StringName {
415 fn from(path: &NodePath) -> Self {
416 Self::from(&GString::from(path))
417 }
418}
419
420// ----------------------------------------------------------------------------------------------------------------------------------------------
421// Ordering
422
423/// Type that implements `Ord` for `StringNames`.
424///
425/// See [`StringName::transient_ord()`].
426pub struct TransientStringNameOrd<'a>(&'a StringName);
427
428impl PartialEq for TransientStringNameOrd<'_> {
429 fn eq(&self, other: &Self) -> bool {
430 self.0 == other.0
431 }
432}
433
434impl Eq for TransientStringNameOrd<'_> {}
435
436impl PartialOrd for TransientStringNameOrd<'_> {
437 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
438 Some(self.cmp(other))
439 }
440}
441
442impl Ord for TransientStringNameOrd<'_> {
443 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
444 // SAFETY: builtin operator provided by Godot.
445 let op_less = |lhs, rhs| unsafe {
446 let mut result = false;
447 sys::builtin_call! {
448 string_name_operator_less(lhs, rhs, result.sys_mut())
449 }
450 result
451 };
452
453 let self_ptr = self.0.sys();
454 let other_ptr = other.0.sys();
455
456 if op_less(self_ptr, other_ptr) {
457 std::cmp::Ordering::Less
458 } else if op_less(other_ptr, self_ptr) {
459 std::cmp::Ordering::Greater
460 } else if self.eq(other) {
461 std::cmp::Ordering::Equal
462 } else {
463 panic!(
464 "Godot provides inconsistent StringName ordering for \"{}\" and \"{}\"",
465 self.0, other.0
466 );
467 }
468 }
469}
470
471// ----------------------------------------------------------------------------------------------------------------------------------------------
472// serde support
473
474#[cfg(feature = "serde")] #[cfg_attr(published_docs, doc(cfg(feature = "serde")))]
475mod serialize {
476 use std::fmt::Formatter;
477
478 use serde::de::{Error, Visitor};
479 use serde::{Deserialize, Deserializer, Serialize, Serializer};
480
481 use super::*;
482
483 // For "Available on crate feature `serde`" in docs. Cannot be inherited from module. Also does not support #[derive] (e.g. in Vector2).
484 #[cfg_attr(published_docs, doc(cfg(feature = "serde")))]
485 impl Serialize for StringName {
486 #[inline]
487 fn serialize<S>(
488 &self,
489 serializer: S,
490 ) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
491 where
492 S: Serializer,
493 {
494 serializer.serialize_str(&self.to_string())
495 }
496 }
497
498 #[cfg_attr(published_docs, doc(cfg(feature = "serde")))]
499 impl<'de> Deserialize<'de> for StringName {
500 #[inline]
501 fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
502 where
503 D: Deserializer<'de>,
504 {
505 struct StringNameVisitor;
506 impl Visitor<'_> for StringNameVisitor {
507 type Value = StringName;
508
509 fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
510 formatter.write_str("a StringName")
511 }
512
513 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
514 where
515 E: Error,
516 {
517 Ok(StringName::from(s))
518 }
519 }
520
521 deserializer.deserialize_str(StringNameVisitor)
522 }
523 }
524}
525
526// TODO(v0.4.x): consider re-exposing in public API. Open questions: thread-safety, performance, memory leaks, global overhead.
527// Possibly in a more general StringName cache, similar to ClassId. See https://github.com/godot-rust/gdext/pull/1316.
528/// Creates and gets a reference to a static `StringName` from a ASCII/Latin-1 `c"string"`.
529///
530/// 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.
531#[macro_export]
532macro_rules! static_sname {
533 ($str:literal) => {{
534 use std::sync::OnceLock;
535
536 let c_str: &'static std::ffi::CStr = $str;
537 static SNAME: OnceLock<StringName> = OnceLock::new();
538 SNAME.get_or_init(|| StringName::__cstr_with_static(c_str, true))
539 }};
540}