Skip to main content

godot_core/builtin/strings/
gstring.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::convert::Infallible;
9use std::fmt;
10use std::fmt::Write;
11
12use godot_ffi as sys;
13use sys::{ExtVariantType, GodotFfi, ffi_methods};
14
15use crate::builtin::strings::{Encoding, pad_if_needed};
16use crate::builtin::{GodotStringExt, NodePath, StringName, Variant, inner};
17use crate::meta::AsArg;
18use crate::meta::error::StringError;
19use crate::{impl_shared_string_api, meta};
20
21/// Godot's reference counted string type.
22///
23/// This is the Rust binding of GDScript's `String` type. It represents the native string class used within the Godot engine,
24/// and as such has different memory layout and characteristics than `std::string::String`.
25///
26/// `GString` uses copy-on-write semantics and is cheap to clone. Modifying a string may trigger a copy, if that instance shares
27/// its backing storage with other strings.
28///
29/// Note that `GString` is not immutable, but it offers a very limited set of write APIs. Most operations return new strings.
30/// In order to modify Godot strings, it's often easiest to convert them to Rust strings, perform the modifications and convert back.
31///
32/// # `GString` vs. `String`
33/// When interfacing with the Godot engine API, you often have the choice between `String` and `GString`. In user-declared methods
34/// exposed to Godot through the `#[func]` attribute, both types can be used as parameters and return types, and conversions
35/// are done transparently. For auto-generated binding APIs in `godot::classes`, both parameters and return types are `GString`.
36/// Parameters are declared as `impl AsArg<GString>`, allowing you to be more flexible with arguments such as `"some_string"`.
37///
38/// As a general guideline, use `GString` if:
39/// * your strings are very large, so you can avoid copying them
40/// * you need specific operations only available in Godot (e.g. `sha256_text()`, `c_escape()`, ...)
41/// * you primarily pass them between different Godot APIs, without string processing in user code
42///
43/// Use Rust's `String` if:
44/// * you need to modify the string
45/// * you would like to decouple part of your code from Godot (e.g. independent game logic, standalone tests)
46/// * you want a standard type for interoperability with third-party code (e.g. `regex` crate)
47/// * you have a large number of method calls per string instance (which are more expensive due to indirectly calling into Godot)
48/// * you need UTF-8 encoding (`GString` uses UTF-32)
49///
50/// # All string types + conversions
51/// | String type                                | Intended use case       | Encoding  | Convert to                                 |
52/// |--------------------------------------------|-------------------------|-----------|--------------------------------------------|
53/// | **`GString`**                              | General purpose         | UTF-32    | `to_gstring()`                             |
54/// | [`StringName`][crate::builtin::StringName] | Interned names          | UTF-32    | [`to_string_name()`][Self::to_string_name] |
55/// | [`NodePath`][crate::builtin::NodePath]     | Scene-node paths        | segmented | [`to_node_path()`][Self::to_node_path]     |
56/// | `String`                                   | Owned, general purpose  | UTF-8     | [`to_string()`](#method.to_string)         |
57/// | `&str`                                     | Borrowed slice          | UTF-8     | _not supported_                            |
58/// | `&[char]`                                  | Borrowed slice (UTF-32) | UTF-32    | [`chars()`][Self::chars]                   |
59///
60/// See also `GString` constructors for more low-level conversions from `&[u8]` and `CStr`.
61///
62/// # Null bytes
63/// Note that Godot ignores any bytes after a null-byte. This means that for instance `"hello, world!"` and `"hello, world!\0 ignored by Godot"`
64/// will be treated as the same string if converted to a `GString`.
65///
66/// # Godot docs
67/// [`String` (stable)](https://docs.godotengine.org/en/stable/classes/class_string.html)
68#[doc(alias = "String")]
69// #[repr] is needed on GString itself rather than the opaque field, because PackedStringArray::as_slice() relies on a packed representation.
70#[repr(transparent)]
71pub struct GString {
72    _opaque: sys::types::OpaqueString,
73}
74
75// SAFETY: The Godot implementation of String uses an atomic copy on write pointer, making this thread-safe as we never write to it unless we own it.
76unsafe impl Send for GString {}
77
78impl GString {
79    /// Construct a new empty `GString`.
80    pub fn new() -> Self {
81        Self::default()
82    }
83
84    /// Convert string from bytes with given encoding, returning `Err` on validation errors.
85    ///
86    /// Intermediate `NUL` characters are not accepted in Godot and always return `Err`.
87    ///
88    /// Some notes on the encodings:
89    /// - **Latin-1:** Since every byte is a valid Latin-1 character, no validation besides the `NUL` byte is performed.
90    ///   It is your responsibility to ensure that the input is meaningful under Latin-1.
91    /// - **ASCII**: Subset of Latin-1, which is additionally validated to be valid, non-`NUL` ASCII characters.
92    /// - **UTF-8**: The input is validated to be UTF-8.
93    ///
94    /// Specifying incorrect encoding is safe, but may result in unintended string values.
95    pub fn try_from_bytes(bytes: &[u8], encoding: Encoding) -> Result<Self, StringError> {
96        Self::try_from_bytes_with_nul_check(bytes, encoding, true)
97    }
98
99    /// Convert string from C-string with given encoding, returning `Err` on validation errors.
100    ///
101    /// Convenience function for [`try_from_bytes()`](Self::try_from_bytes); see its docs for more information.
102    pub fn try_from_cstr(cstr: &std::ffi::CStr, encoding: Encoding) -> Result<Self, StringError> {
103        Self::try_from_bytes_with_nul_check(cstr.to_bytes(), encoding, false)
104    }
105
106    pub(super) fn try_from_bytes_with_nul_check(
107        bytes: &[u8],
108        encoding: Encoding,
109        check_nul: bool,
110    ) -> Result<Self, StringError> {
111        match encoding {
112            Encoding::Ascii => {
113                // If the bytes are ASCII, we can fall back to Latin-1, which is always valid (except for NUL).
114                // is_ascii() does *not* check for the NUL byte, so the check in the Latin-1 branch is still necessary.
115                if bytes.is_ascii() {
116                    Self::try_from_bytes_with_nul_check(bytes, Encoding::Latin1, check_nul)
117                        .map_err(|_e| StringError::new("intermediate NUL byte in ASCII string"))
118                } else {
119                    Err(StringError::new("invalid ASCII"))
120                }
121            }
122            Encoding::Latin1 => {
123                // Intermediate NUL bytes are not accepted in Godot. Both ASCII + Latin-1 encodings need to explicitly check for this.
124                if check_nul && bytes.contains(&0) {
125                    // Error overwritten when called from ASCII branch.
126                    return Err(StringError::new("intermediate NUL byte in Latin-1 string"));
127                }
128
129                let s = unsafe {
130                    Self::new_with_string_uninit(|string_ptr| {
131                        let ctor = sys::thread_safe().string_new_with_latin1_chars_and_len;
132                        ctor(
133                            string_ptr,
134                            bytes.as_ptr() as *const std::ffi::c_char,
135                            bytes.len() as i64,
136                        );
137                    })
138                };
139                Ok(s)
140            }
141            Encoding::Utf8 => {
142                // from_utf8() also checks for intermediate NUL bytes.
143                let utf8 = std::str::from_utf8(bytes);
144
145                utf8.map(GString::from)
146                    .map_err(|e| StringError::with_source("invalid UTF-8", e))
147            }
148        }
149    }
150
151    /// Number of characters in the string.
152    ///
153    /// _Godot equivalent: `length`_
154    #[doc(alias = "length")]
155    pub fn len(&self) -> usize {
156        self.as_inner().length().try_into().unwrap()
157    }
158
159    crate::declare_hash_u32_method! {
160        /// Returns a 32-bit integer hash value representing the string.
161    }
162
163    /// Gets the UTF-32 character slice from a `GString`.
164    pub fn chars(&self) -> &[char] {
165        // SAFETY: Since 4.1, Godot ensures valid UTF-32, making interpreting as char slice safe.
166        // See https://github.com/godotengine/godot/pull/74760.
167        let (ptr, len) = self.raw_slice();
168
169        // Even when len == 0, from_raw_parts requires ptr != null.
170        if ptr.is_null() {
171            return &[];
172        }
173
174        unsafe { std::slice::from_raw_parts(ptr, len) }
175    }
176
177    /// Returns the raw pointer and length of the internal UTF-32 character array.
178    ///
179    /// This is used by `StringName::chars()` in Godot 4.5+ where the buffer is shared via reference counting.
180    /// Since Godot 4.1, the buffer contains valid UTF-32.
181    pub(crate) fn raw_slice(&self) -> (*const char, usize) {
182        let s = self.string_sys();
183
184        let len: sys::GDExtensionInt;
185        let ptr: *const sys::char32_t;
186        unsafe {
187            len = (sys::thread_safe().string_to_utf32_chars)(s, std::ptr::null_mut(), 0);
188            ptr = (sys::thread_safe().string_operator_index_const)(s, 0);
189        }
190
191        (ptr.cast(), len as usize)
192    }
193
194    ffi_methods! {
195        type sys::GDExtensionStringPtr = *mut Self;
196
197        fn new_from_string_sys = new_from_sys;
198        fn new_with_string_uninit = new_with_uninit;
199        fn string_sys = sys;
200        fn string_sys_mut = sys_mut;
201    }
202
203    /// Consumes self and turns it into a sys-ptr, should be used together with [`from_owned_string_sys`](Self::from_owned_string_sys).
204    ///
205    /// This will leak memory unless `from_owned_string_sys` is called on the returned pointer.
206    pub(crate) fn into_owned_string_sys(self) -> sys::GDExtensionStringPtr {
207        sys::static_assert_eq_size_align!(StringName, sys::types::OpaqueString);
208
209        let leaked = Box::into_raw(Box::new(self));
210        leaked.cast()
211    }
212
213    /// Creates a `GString` from a sys-ptr without incrementing the refcount.
214    ///
215    /// # Safety
216    ///
217    /// * Must only be used on a pointer returned from a call to [`into_owned_string_sys`](Self::into_owned_string_sys).
218    /// * Must not be called more than once on the same pointer.
219    pub(crate) unsafe fn from_owned_string_sys(ptr: sys::GDExtensionStringPtr) -> Self {
220        sys::static_assert_eq_size_align!(StringName, sys::types::OpaqueString);
221
222        let ptr = ptr.cast::<Self>();
223
224        // SAFETY: `ptr` was returned from a call to `into_owned_string_sys`, which means it was created by a call to
225        // `Box::into_raw`, thus we can use `Box::from_raw` here. Additionally, this is only called once on this pointer.
226        let boxed = unsafe { Box::from_raw(ptr) };
227        *boxed
228    }
229
230    /// Convert a `GString` sys pointer to a mutable reference with unbounded lifetime.
231    ///
232    /// # Safety
233    ///
234    /// - `ptr` must point to a live `GString` for the duration of `'a`.
235    /// - Must be exclusive - no other reference to given `GString` instance can exist for the duration of `'a`.
236    pub(crate) unsafe fn borrow_string_sys_mut<'a>(ptr: sys::GDExtensionStringPtr) -> &'a mut Self {
237        sys::static_assert_eq_size_align!(StringName, sys::types::OpaqueString);
238
239        unsafe { &mut *(ptr.cast::<GString>()) }
240    }
241
242    /// Moves this string into a string sys pointer. This is the same as using [`GodotFfi::move_return_ptr`].
243    ///
244    /// # Safety
245    ///
246    /// `dst` must be a valid string pointer.
247    pub(crate) unsafe fn move_into_string_ptr(self, dst: sys::GDExtensionStringPtr) {
248        let dst: sys::GDExtensionTypePtr = dst.cast();
249
250        unsafe { self.move_return_ptr(dst, sys::PtrcallType::Standard) };
251    }
252
253    pub(crate) fn as_inner(&self) -> inner::InnerString<'_> {
254        inner::InnerString::from_outer(self)
255    }
256}
257
258// SAFETY:
259// - `move_return_ptr`
260//   Nothing special needs to be done beyond a `std::mem::swap` when returning a String.
261//   So we can just use `ffi_methods`.
262//
263// - `from_arg_ptr`
264//   Strings are properly initialized through a `from_sys` call, but the ref-count should be
265//   incremented as that is the callee's responsibility. Which we do by calling
266//   `std::mem::forget(string.clone())`.
267unsafe impl GodotFfi for GString {
268    const VARIANT_TYPE: ExtVariantType = ExtVariantType::Concrete(sys::VariantType::STRING);
269
270    ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. }
271}
272
273meta::impl_godot_as_self!(GString: ByRef);
274
275// These run through `sys::thread_safe_lifecycle()`, so they are thread-safe (string value types only touch caller-owned memory).
276impl_builtin_traits! {
277    thread_safe for GString {
278        Default => string_construct_default;
279        Clone => string_construct_copy;
280        Drop => string_destroy;
281        Eq => string_operator_equal;
282        Ord => string_operator_less;
283        Hash;
284    }
285}
286
287impl_shared_string_api! {
288    builtin: GString,
289    builtin_mod: gstring,
290}
291
292impl fmt::Display for GString {
293    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
294        pad_if_needed(f, |f| {
295            for ch in self.chars() {
296                f.write_char(*ch)?;
297            }
298
299            Ok(())
300        })
301    }
302}
303
304/// Uses literal syntax from GDScript: `"string"`
305impl fmt::Debug for GString {
306    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
307        // Reuse Display impl.
308        write!(f, "\"{self}\"")
309    }
310}
311
312// ----------------------------------------------------------------------------------------------------------------------------------------------
313// Comparison with Rust strings
314
315// API design:
316// * StringName and NodePath don't implement PartialEq<&str> yet, because they require allocation (convert to GString).
317//   == should ideally not allocate.
318// * Reverse `impl PartialEq<GString> for &str` is not implemented now. Comparisons usually take the form of variable == "literal".
319//   Can be added later if there are good use-cases.
320
321impl PartialEq<&str> for GString {
322    fn eq(&self, other: &&str) -> bool {
323        self.chars().iter().copied().eq(other.chars())
324    }
325}
326
327// ----------------------------------------------------------------------------------------------------------------------------------------------
328// Conversion from/into Rust string-types
329
330impl From<&str> for GString {
331    fn from(s: &str) -> Self {
332        s.to_gstring()
333    }
334}
335
336impl From<&[char]> for GString {
337    fn from(chars: &[char]) -> Self {
338        chars.to_gstring()
339    }
340}
341
342impl From<&String> for GString {
343    fn from(value: &String) -> Self {
344        value.as_str().into()
345    }
346}
347
348impl From<&GString> for String {
349    fn from(string: &GString) -> Self {
350        unsafe {
351            let len = (sys::thread_safe().string_to_utf8_chars)(
352                string.string_sys(),
353                std::ptr::null_mut(),
354                0,
355            );
356
357            assert!(len >= 0);
358            let mut buf = vec![0u8; len as usize];
359
360            (sys::thread_safe().string_to_utf8_chars)(
361                string.string_sys(),
362                buf.as_mut_ptr() as *mut std::ffi::c_char,
363                len,
364            );
365
366            // Note: could use from_utf8_unchecked() but for now prefer safety
367            String::from_utf8(buf).expect("String::from_utf8")
368        }
369    }
370}
371
372impl From<GString> for String {
373    /// Converts this `GString` to a `String`.
374    ///
375    /// This is identical to `String::from(&string)`, and as such there is no performance benefit.
376    fn from(string: GString) -> Self {
377        Self::from(&string)
378    }
379}
380
381impl std::str::FromStr for GString {
382    type Err = Infallible;
383
384    fn from_str(s: &str) -> Result<Self, Self::Err> {
385        Ok(Self::from(s))
386    }
387}
388
389// ----------------------------------------------------------------------------------------------------------------------------------------------
390// Conversion from other Godot string-types
391
392impl From<&StringName> for GString {
393    fn from(string: &StringName) -> Self {
394        string.to_gstring()
395    }
396}
397
398impl From<&NodePath> for GString {
399    fn from(path: &NodePath) -> Self {
400        path.to_gstring()
401    }
402}
403
404#[cfg(feature = "serde")] #[cfg_attr(published_docs, doc(cfg(feature = "serde")))]
405mod serialize {
406    use std::fmt::Formatter;
407
408    use serde::de::{Error, Visitor};
409    use serde::{Deserialize, Deserializer, Serialize, Serializer};
410
411    use super::*;
412
413    // For "Available on crate feature `serde`" in docs. Cannot be inherited from module. Also does not support #[derive] (e.g. in Vector2).
414    #[cfg_attr(published_docs, doc(cfg(feature = "serde")))]
415    impl Serialize for GString {
416        #[inline]
417        fn serialize<S>(
418            &self,
419            serializer: S,
420        ) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
421        where
422            S: Serializer,
423        {
424            serializer.serialize_str(&self.to_string())
425        }
426    }
427
428    #[cfg_attr(published_docs, doc(cfg(feature = "serde")))]
429    impl<'de> Deserialize<'de> for GString {
430        #[inline]
431        fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
432        where
433            D: Deserializer<'de>,
434        {
435            struct GStringVisitor;
436            impl Visitor<'_> for GStringVisitor {
437                type Value = GString;
438
439                fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
440                    formatter.write_str("a GString")
441                }
442
443                fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
444                where
445                    E: Error,
446                {
447                    Ok(GString::from(s))
448                }
449            }
450
451            deserializer.deserialize_str(GStringVisitor)
452        }
453    }
454}