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