godot_core/builtin/variant/
mod.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, ptr};
9
10use godot_ffi as sys;
11use sys::{ffi_methods, interface_fn, GodotFfi};
12
13use crate::builtin::{
14    GString, StringName, VariantArray, VariantDispatch, VariantOperator, VariantType,
15};
16use crate::classes;
17use crate::meta::error::{ConvertError, FromVariantError};
18use crate::meta::{
19    arg_into_ref, ffi_variant_type, ArrayElement, AsArg, ExtVariantType, FromGodot, GodotType,
20    ToGodot,
21};
22
23mod impls;
24
25/// Godot variant type, able to store a variety of different types.
26///
27/// While Godot variants do not appear very frequently in Rust due to their lack of compile-time type-safety, they are central to all sorts of
28/// dynamic APIs. For example, if you want to call a method on an object based on a string, you will need variants to store arguments and return
29/// value.  
30///
31/// # Conversions
32///
33/// For type conversions, please read the [`godot::meta` module docs][crate::meta].
34///
35/// # Godot docs
36///
37/// [`Variant` (stable)](https://docs.godotengine.org/en/stable/classes/class_variant.html)
38// We rely on the layout of `Variant` being the same as Godot's layout in `borrow_slice` and `borrow_slice_mut`.
39#[repr(transparent)]
40pub struct Variant {
41    _opaque: sys::types::OpaqueVariant,
42}
43
44impl Variant {
45    /// Create an empty variant (`null` value in GDScript).
46    ///
47    /// If a Godot engine API accepts object (not variant) parameters and you'd like to pass `null`, use
48    /// [`Gd::null_arg()`][crate::obj::Gd::null_arg] instead.
49    pub fn nil() -> Self {
50        Self::default()
51    }
52
53    /// Create a variant holding a non-nil value.
54    ///
55    /// Equivalent to [`value.to_variant()`][ToGodot::to_variant], but consumes the argument.
56    pub fn from<T: ToGodot>(value: T) -> Self {
57        value.to_variant()
58    }
59
60    /// ⚠️ Convert to type `T`, panicking on failure.
61    ///
62    /// Equivalent to [`T::from_variant(&self)`][FromGodot::from_variant].
63    ///
64    /// # Panics
65    /// When this variant holds a different type.
66    pub fn to<T: FromGodot>(&self) -> T {
67        T::from_variant(self)
68    }
69
70    /// Convert to type `T`, returning `Err` on failure.
71    ///
72    /// The conversion only succeeds if the type stored in the variant matches `T`'s FFI representation.
73    /// For lenient conversions like in GDScript, use [`try_to_relaxed()`](Self::try_to_relaxed) instead.
74    ///
75    /// Equivalent to [`T::try_from_variant(&self)`][FromGodot::try_from_variant].
76    pub fn try_to<T: FromGodot>(&self) -> Result<T, ConvertError> {
77        T::try_from_variant(self)
78    }
79
80    /// Convert to `T` using Godot's less strict conversion rules.
81    ///
82    /// More lenient than [`try_to()`](Self::try_to), which only allows exact type matches.
83    /// Enables conversions between related types that Godot considers compatible under its conversion rules.
84    ///
85    /// Precisely matches GDScript's behavior to converts arguments, when a function declares a parameter of different type.
86    ///
87    /// # Conversion diagram
88    /// Exhaustive list of all possible conversions, as of Godot 4.4. The arrow `──►` means "converts to".
89    ///
90    /// ```text
91    ///                                                               * ───► Variant
92    ///                                                               * ───► itself (reflexive)
93    ///         float          StringName
94    ///         ▲   ▲             ▲                            Vector2 ◄───► Vector2i
95    ///        ╱     ╲            │                            Vector3 ◄───► Vector3i
96    ///       ▼       ▼           ▼                            Vector4 ◄───► Vector4i
97    ///    bool ◄───► int       GString ◄───► NodePath           Rect2 ◄───► Rect2i
98    ///                 ╲       ╱
99    ///                  ╲     ╱                              Array<T> ◄───► PackedArray<T>
100    ///                   ▼   ▼
101    ///                   Color                                   Gd<T> ───► Rid
102    ///                                                             nil ───► Option<Gd<T>>
103    ///
104    ///                                Basis ◄───► Quaternion
105    ///                                    ╲       ╱
106    ///                                     ╲     ╱
107    ///                                      ▼   ▼
108    ///                 Transform2D ◄───► Transform3D ◄───► Projection
109    /// ```
110    ///
111    /// # Godot implementation details
112    /// See [GDExtension interface](https://github.com/godotengine/godot/blob/4.4-stable/core/extension/gdextension_interface.h#L1353-L1364)
113    /// and [C++ implementation](https://github.com/godotengine/godot/blob/4.4-stable/core/variant/variant.cpp#L532) (Godot 4.4 at the time of
114    /// writing). The "strict" part refers to excluding certain conversions, such as between `int` and `GString`.
115    ///
116    // ASCII arsenal: / ╱ ⟋ ⧸ ⁄ ╱ ↗ ╲ \ ╲ ⟍ ⧹ ∖
117    pub fn try_to_relaxed<T: FromGodot>(&self) -> Result<T, ConvertError> {
118        try_from_variant_relaxed(self)
119    }
120
121    /// Helper function for relaxed variant conversion with panic on failure.
122    /// Similar to [`to()`](Self::to) but uses relaxed conversion rules.
123    pub(crate) fn to_relaxed_or_panic<T, F>(&self, context: F) -> T
124    where
125        T: FromGodot,
126        F: FnOnce() -> String,
127    {
128        self.try_to_relaxed::<T>()
129            .unwrap_or_else(|err| panic!("{}: {err}", context()))
130    }
131
132    /// Checks whether the variant is empty (`null` value in GDScript).
133    ///
134    /// See also [`get_type()`][Self::get_type].
135    pub fn is_nil(&self) -> bool {
136        // Use get_type() rather than sys_type(), to also cover nullptr OBJECT as NIL
137        self.get_type() == VariantType::NIL
138    }
139
140    /// Returns the type that is currently held by this variant.
141    ///
142    /// If this variant holds a type `Object` but no instance (represented as a null object pointer), then `Nil` will be returned for
143    /// consistency. This may deviate from Godot behavior -- for example, calling [`Node::get_node_or_null()`][crate::classes::Node::get_node_or_null]
144    ///  with an invalid path returns a variant that has type `Object` but acts like `Nil` for all practical purposes.
145    pub fn get_type(&self) -> VariantType {
146        let sys_type = self.sys_type();
147
148        // There is a special case when the Variant has type OBJECT, but the Object* is null.
149        let is_null_object = if sys_type == sys::GDEXTENSION_VARIANT_TYPE_OBJECT {
150            // SAFETY: we checked that the raw type is OBJECT, so we can interpret the type-ptr as address of an object-ptr.
151            let object_ptr = unsafe {
152                crate::obj::raw_object_init(|type_ptr| {
153                    let converter = sys::builtin_fn!(object_from_variant);
154                    converter(type_ptr, sys::SysPtr::force_mut(self.var_sys()));
155                })
156            };
157
158            object_ptr.is_null()
159        } else {
160            false
161        };
162
163        if is_null_object {
164            VariantType::NIL
165        } else {
166            VariantType::from_sys(sys_type)
167        }
168    }
169
170    /// For variants holding an object, returns the object's instance ID.
171    ///
172    /// If the variant is not an object, returns `None`.
173    ///
174    /// # Panics
175    /// If the variant holds an object and that object is dead.
176    ///
177    /// If you want to detect this case, use [`try_to::<Gd<...>>()`](Self::try_to). If you want to retrieve the previous instance ID of a
178    /// freed object for whatever reason, use [`object_id_unchecked()`][Self::object_id_unchecked]. This method is only available from
179    /// Godot 4.4 onwards.
180    pub fn object_id(&self) -> Option<crate::obj::InstanceId> {
181        #[cfg(since_api = "4.4")]
182        {
183            assert!(
184                self.get_type() != VariantType::OBJECT || self.is_object_alive(),
185                "Variant::object_id(): object has been freed"
186            );
187            self.object_id_unchecked()
188        }
189
190        #[cfg(before_api = "4.4")]
191        {
192            use crate::meta::error::{ErrorKind, FromVariantError};
193            match self.try_to::<crate::obj::Gd<crate::classes::Object>>() {
194                Ok(obj) => Some(obj.instance_id_unchecked()),
195                Err(c)
196                    if matches!(
197                        c.kind(),
198                        ErrorKind::FromVariant(FromVariantError::DeadObject)
199                    ) =>
200                {
201                    panic!("Variant::object_id(): object has been freed")
202                }
203                _ => None, // other conversion errors
204            }
205        }
206    }
207
208    /// For variants holding an object, returns the object's instance ID.
209    ///
210    /// If the variant is not an object, returns `None`.
211    ///
212    /// If the object is dead, the instance ID is still returned, similar to [`Gd::instance_id_unchecked()`][crate::obj::Gd::instance_id_unchecked].
213    /// Unless you have a very good reason to use this, we recommend using [`object_id()`][Self::object_id] instead.
214    #[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
215    pub fn object_id_unchecked(&self) -> Option<crate::obj::InstanceId> {
216        // SAFETY: safe to call for non-object variants (returns 0).
217        let raw_id: u64 = unsafe { interface_fn!(variant_get_object_instance_id)(self.var_sys()) };
218
219        crate::obj::InstanceId::try_from_u64(raw_id)
220    }
221
222    /// ⚠️ Calls the specified `method` with the given `args`.
223    ///
224    /// Supports `Object` as well as built-ins with methods (e.g. `Array`, `Vector3`, `GString`, etc.).
225    ///
226    /// # Panics
227    /// * If `self` is not a variant type which supports method calls.
228    /// * If the method does not exist or the signature is not compatible with the passed arguments.
229    /// * If the call causes an error.
230    #[inline]
231    pub fn call(&self, method: impl AsArg<StringName>, args: &[Variant]) -> Variant {
232        arg_into_ref!(method);
233        self.call_inner(method, args)
234    }
235
236    fn call_inner(&self, method: &StringName, args: &[Variant]) -> Variant {
237        let args_sys: Vec<_> = args.iter().map(|v| v.var_sys()).collect();
238        let mut error = sys::default_call_error();
239
240        let result = unsafe {
241            Variant::new_with_var_uninit(|variant_ptr| {
242                interface_fn!(variant_call)(
243                    sys::SysPtr::force_mut(self.var_sys()),
244                    method.string_sys(),
245                    args_sys.as_ptr(),
246                    args_sys.len() as i64,
247                    variant_ptr,
248                    ptr::addr_of_mut!(error),
249                )
250            })
251        };
252
253        if error.error != sys::GDEXTENSION_CALL_OK {
254            let arg_types: Vec<_> = args.iter().map(Variant::get_type).collect();
255            sys::panic_call_error(&error, "call", &arg_types);
256        }
257        result
258    }
259
260    /// Evaluates an expression using a GDScript operator.
261    ///
262    /// Returns the result of the operation, or `None` if the operation is not defined for the given operand types.
263    ///
264    /// Recommended to be used with fully-qualified call syntax.
265    /// For example, `Variant::evaluate(&a, &b, VariantOperator::Add)` is equivalent to `a + b` in GDScript.
266    pub fn evaluate(&self, rhs: &Variant, op: VariantOperator) -> Option<Variant> {
267        use crate::obj::EngineEnum;
268
269        let op_sys = op.ord() as sys::GDExtensionVariantOperator;
270        let mut is_valid = false as u8;
271
272        let result = unsafe {
273            Self::new_with_var_uninit(|variant_ptr| {
274                interface_fn!(variant_evaluate)(
275                    op_sys,
276                    self.var_sys(),
277                    rhs.var_sys(),
278                    variant_ptr,
279                    ptr::addr_of_mut!(is_valid),
280                )
281            })
282        };
283
284        if is_valid == 1 {
285            Some(result)
286        } else {
287            None
288        }
289    }
290
291    pub(crate) fn sys_type(&self) -> sys::GDExtensionVariantType {
292        unsafe {
293            let ty: sys::GDExtensionVariantType = interface_fn!(variant_get_type)(self.var_sys());
294            ty
295        }
296    }
297
298    /// Return Godot's string representation of the variant.
299    ///
300    /// See also `Display` impl.
301    #[allow(unused_mut)] // result
302    pub fn stringify(&self) -> GString {
303        let mut result = GString::new();
304        unsafe {
305            interface_fn!(variant_stringify)(self.var_sys(), result.string_sys_mut());
306        }
307        result
308    }
309
310    /// Return Godot's hash value for the variant.
311    ///
312    /// _Godot equivalent : `@GlobalScope.hash()`_
313    pub fn hash_u32(&self) -> u32 {
314        // @GlobalScope.hash() actually calls the VariantUtilityFunctions::hash(&Variant) function (cpp).
315        // This function calls the passed reference's `hash` method, which returns a uint32_t.
316        // Therefore, casting this function to u32 is always fine.
317        unsafe { interface_fn!(variant_hash)(self.var_sys()) }
318            .try_into()
319            .expect("Godot hashes are uint32_t")
320    }
321
322    #[deprecated = "renamed to hash_u32 and type changed to u32"]
323    pub fn hash(&self) -> i64 {
324        unsafe { interface_fn!(variant_hash)(self.var_sys()) }
325    }
326
327    /// Interpret the `Variant` as `bool`.
328    ///
329    /// Returns `false` only if the variant's current value is the default value for its type. For example:
330    /// - `nil` for the nil type
331    /// - `false` for bool
332    /// - zero for numeric types
333    /// - empty string
334    /// - empty container (array, packed array, dictionary)
335    /// - default-constructed other builtins (e.g. zero vector, degenerate plane, zero RID, etc...)
336    pub fn booleanize(&self) -> bool {
337        // See Variant::is_zero(), roughly https://github.com/godotengine/godot/blob/master/core/variant/variant.cpp#L859.
338
339        unsafe { interface_fn!(variant_booleanize)(self.var_sys()) != 0 }
340    }
341
342    /// Assuming that this is of type `OBJECT`, checks whether the object is dead.
343    ///
344    /// Does not check again that the variant has type `OBJECT`.
345    pub(crate) fn is_object_alive(&self) -> bool {
346        debug_assert_eq!(self.get_type(), VariantType::OBJECT);
347
348        crate::global::is_instance_valid(self)
349
350        // In case there are ever problems with this approach, alternative implementation:
351        // self.stringify() != "<Freed Object>".into()
352    }
353
354    // Conversions from/to Godot C++ `Variant*` pointers
355    ffi_methods! {
356        type sys::GDExtensionVariantPtr = *mut Self;
357
358        fn new_from_var_sys = new_from_sys;
359        fn new_with_var_uninit = new_with_uninit;
360        fn new_with_var_init = new_with_init;
361        fn var_sys = sys;
362        fn var_sys_mut = sys_mut;
363    }
364}
365
366// All manually implemented unsafe functions on `Variant`.
367// Deny `unsafe_op_in_unsafe_fn` so we don't forget to check safety invariants.
368#[doc(hidden)]
369#[deny(unsafe_op_in_unsafe_fn)]
370impl Variant {
371    /// Moves this variant into a variant sys pointer. This is the same as using [`GodotFfi::move_return_ptr`].
372    ///
373    /// # Safety
374    ///
375    /// `dst` must be a valid variant pointer.
376    pub(crate) unsafe fn move_into_var_ptr(self, dst: sys::GDExtensionVariantPtr) {
377        let dst: sys::GDExtensionTypePtr = dst.cast();
378        // SAFETY: `dst` is a valid Variant pointer. Additionally `Variant` doesn't behave differently for `Standard` and `Virtual`
379        // pointer calls.
380        unsafe {
381            self.move_return_ptr(dst, sys::PtrcallType::Standard);
382        }
383    }
384
385    /// Fallible construction of a `Variant` using a fallible initialization function.
386    ///
387    /// # Safety
388    ///
389    /// If `init_fn` returns `Ok(())`, then it must have initialized the pointer passed to it in accordance with [`GodotFfi::new_with_uninit`].
390    #[doc(hidden)]
391    pub unsafe fn new_with_var_uninit_result<E>(
392        init_fn: impl FnOnce(sys::GDExtensionUninitializedVariantPtr) -> Result<(), E>,
393    ) -> Result<Self, E> {
394        // Relies on current macro expansion of from_var_sys_init() having a certain implementation.
395
396        let mut raw = std::mem::MaybeUninit::<Variant>::uninit();
397
398        let var_uninit_ptr =
399            raw.as_mut_ptr() as <sys::GDExtensionVariantPtr as sys::SysPtr>::Uninit;
400
401        // SAFETY: `map` only runs the provided closure for the `Ok(())` variant, in which case `raw` has definitely been initialized.
402        init_fn(var_uninit_ptr).map(|_success| unsafe { raw.assume_init() })
403    }
404
405    /// Convert a `Variant` sys pointer to a reference to a `Variant`.
406    ///
407    /// # Safety
408    ///
409    /// `ptr` must point to a live `Variant` for the duration of `'a`.
410    pub(crate) unsafe fn borrow_var_sys<'a>(ptr: sys::GDExtensionConstVariantPtr) -> &'a Variant {
411        sys::static_assert_eq_size_align!(Variant, sys::types::OpaqueVariant);
412
413        // SAFETY: `ptr` is a pointer to a live `Variant` for the duration of `'a`.
414        unsafe { &*(ptr.cast::<Variant>()) }
415    }
416
417    /// Convert an array of `Variant` sys pointers to a slice of `Variant` references all with unbounded lifetimes.
418    ///
419    /// # Safety
420    ///
421    /// Either `variant_ptr_array` is null, or it must be safe to call [`std::slice::from_raw_parts`] with
422    /// `variant_ptr_array` cast to `*const &'a Variant` and `length`.
423    pub(crate) unsafe fn borrow_ref_slice<'a>(
424        variant_ptr_array: *const sys::GDExtensionConstVariantPtr,
425        length: usize,
426    ) -> &'a [&'a Variant] {
427        sys::static_assert_eq_size_align!(Variant, sys::types::OpaqueVariant);
428        // Godot may pass null to signal "no arguments" (e.g. in custom callables).
429        if variant_ptr_array.is_null() {
430            debug_assert_eq!(
431                length, 0,
432                "Variant::unbounded_refs_from_sys(): pointer is null but length is not 0"
433            );
434            return &[];
435        }
436
437        // Note: Raw pointers and references have the same memory layout.
438        // See https://doc.rust-lang.org/reference/type-layout.html#pointers-and-references-layout.
439        let variant_ptr_array = variant_ptr_array.cast::<&Variant>();
440
441        // SAFETY: `variant_ptr_array` isn't null so it is safe to call `from_raw_parts` on the pointer cast to `*const &Variant`.
442        unsafe { std::slice::from_raw_parts(variant_ptr_array, length) }
443    }
444
445    /// Convert an array of `Variant` sys pointers to a slice with unbounded lifetime.
446    ///
447    /// # Safety
448    ///
449    /// Either `variant_array` is null, or it must be safe to call [`std::slice::from_raw_parts`] with
450    /// `variant_array` cast to `*const Variant` and `length`.
451    pub(crate) unsafe fn borrow_slice<'a>(
452        variant_array: sys::GDExtensionConstVariantPtr,
453        length: usize,
454    ) -> &'a [Variant] {
455        sys::static_assert_eq_size_align!(Variant, sys::types::OpaqueVariant);
456
457        // Godot may pass null to signal "no arguments" (e.g. in custom callables).
458        if variant_array.is_null() {
459            debug_assert_eq!(
460                length, 0,
461                "Variant::unbounded_refs_from_sys(): pointer is null but length is not 0"
462            );
463            return &[];
464        }
465
466        let variant_array = variant_array.cast::<Variant>();
467
468        // SAFETY: `variant_array` isn't null so it is safe to call `from_raw_parts` on the pointer cast to `*const Variant`.
469        unsafe { std::slice::from_raw_parts(variant_array, length) }
470    }
471
472    /// Convert an array of `Variant` sys pointers to a mutable slice with unbounded lifetime.
473    ///
474    /// # Safety
475    ///
476    /// Either `variant_array` is null, or it must be safe to call [`std::slice::from_raw_parts_mut`] with
477    /// `variant_array` cast to `*mut Variant` and `length`.
478    pub(crate) unsafe fn borrow_slice_mut<'a>(
479        variant_array: sys::GDExtensionVariantPtr,
480        length: usize,
481    ) -> &'a mut [Variant] {
482        sys::static_assert_eq_size_align!(Variant, sys::types::OpaqueVariant);
483
484        // Godot may pass null to signal "no arguments" (e.g. in custom callables).
485        if variant_array.is_null() {
486            debug_assert_eq!(
487                length, 0,
488                "Variant::unbounded_refs_from_sys(): pointer is null but length is not 0"
489            );
490            return &mut [];
491        }
492
493        let variant_array = variant_array.cast::<Variant>();
494
495        // SAFETY: `variant_array` isn't null so it is safe to call `from_raw_parts_mut` on the pointer cast to `*mut Variant`.
496        unsafe { std::slice::from_raw_parts_mut(variant_array, length) }
497    }
498
499    /// Consumes self and turns it into a sys-ptr, should be used together with [`from_owned_var_sys`](Self::from_owned_var_sys).
500    ///
501    /// This will leak memory unless `from_owned_var_sys` is called on the returned pointer.
502    pub(crate) fn into_owned_var_sys(self) -> sys::GDExtensionVariantPtr {
503        sys::static_assert_eq_size_align!(Variant, sys::types::OpaqueVariant);
504
505        let leaked = Box::into_raw(Box::new(self));
506        leaked.cast()
507    }
508
509    /// Creates a `Variant` from a sys-ptr without incrementing the refcount.
510    ///
511    /// # Safety
512    ///
513    /// * Must only be used on a pointer returned from a call to [`into_owned_var_sys`](Self::into_owned_var_sys).
514    /// * Must not be called more than once on the same pointer.
515    #[deny(unsafe_op_in_unsafe_fn)]
516    pub(crate) unsafe fn from_owned_var_sys(ptr: sys::GDExtensionVariantPtr) -> Self {
517        sys::static_assert_eq_size_align!(Variant, sys::types::OpaqueVariant);
518
519        let ptr = ptr.cast::<Self>();
520
521        // SAFETY: `ptr` was returned from a call to `into_owned_var_sys`, which means it was created by a call to
522        // `Box::into_raw`, thus we can use `Box::from_raw` here. Additionally, this is only called once on this pointer.
523        let boxed = unsafe { Box::from_raw(ptr) };
524        *boxed
525    }
526}
527
528impl ArrayElement for Variant {}
529
530// SAFETY:
531// `from_opaque` properly initializes a dereferenced pointer to an `OpaqueVariant`.
532// `std::mem::swap` is sufficient for returning a value.
533unsafe impl GodotFfi for Variant {
534    const VARIANT_TYPE: ExtVariantType = ExtVariantType::Variant;
535
536    ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. }
537}
538
539crate::meta::impl_godot_as_self!(Variant: ByRef);
540
541impl Default for Variant {
542    fn default() -> Self {
543        unsafe {
544            Self::new_with_var_uninit(|variant_ptr| {
545                interface_fn!(variant_new_nil)(variant_ptr);
546            })
547        }
548    }
549}
550
551impl Clone for Variant {
552    fn clone(&self) -> Self {
553        unsafe {
554            Self::new_with_var_uninit(|variant_ptr| {
555                interface_fn!(variant_new_copy)(variant_ptr, self.var_sys());
556            })
557        }
558    }
559}
560
561impl Drop for Variant {
562    fn drop(&mut self) {
563        unsafe {
564            interface_fn!(variant_destroy)(self.var_sys_mut());
565        }
566    }
567}
568
569// Variant is not Eq because it can contain floats and other types composed of floats.
570impl PartialEq for Variant {
571    fn eq(&self, other: &Self) -> bool {
572        Self::evaluate(self, other, VariantOperator::EQUAL) //.
573            .is_some_and(|v| v.to::<bool>())
574        // If there is no defined conversion (-> None), then they are non-equal.
575    }
576}
577
578impl fmt::Display for Variant {
579    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
580        let s = self.stringify();
581        write!(f, "{s}")
582    }
583}
584
585impl fmt::Debug for Variant {
586    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
587        match self.get_type() {
588            // Special case for arrays: avoids converting to VariantArray (the only Array type in VariantDispatch),
589            // which fails for typed arrays and causes a panic. This can cause an infinite loop with Debug, or abort.
590            // Can be removed if there's ever a "possibly typed" Array type (e.g. OutArray) in the library.
591            VariantType::ARRAY => {
592                // SAFETY: type is checked, and only operation is print (out data flow, no covariant in access).
593                let array = unsafe { VariantArray::from_variant_unchecked(self) };
594                array.fmt(f)
595            }
596
597            // Converting to objects before printing causes their refcount to increment, leading to an Observer effect
598            // where `Debug` actually changes the object statistics. As such, fetch information without instantiating Gd<T>.
599            VariantType::OBJECT => classes::debug_string_variant(self, f, "VariantGd"),
600
601            // VariantDispatch also includes dead objects via `FreedObject` enumerator, which maps to "<Freed Object>".
602            _ => VariantDispatch::from_variant(self).fmt(f),
603        }
604    }
605}
606
607fn try_from_variant_relaxed<T: FromGodot>(variant: &Variant) -> Result<T, ConvertError> {
608    let from_type = variant.get_type();
609    let to_type = match ffi_variant_type::<T>() {
610        ExtVariantType::Variant => {
611            // Converting to Variant always succeeds.
612            return T::try_from_variant(variant);
613        }
614        ExtVariantType::Concrete(to_type) if from_type == to_type => {
615            // If types are the same, use the regular conversion.
616            // This is both an optimization (avoids more FFI) and ensures consistency between strict and relaxed conversions for identical types.
617            return T::try_from_variant(variant);
618        }
619        ExtVariantType::Concrete(to_type) => to_type,
620    };
621
622    // Non-NIL types can technically be converted to NIL according to `variant_can_convert_strict()`, however that makes no sense -- from
623    // neither a type perspective (NIL is unit, not never type), nor a practical one. Disallow any such conversions.
624    if to_type == VariantType::NIL || !can_convert_godot_strict(from_type, to_type) {
625        return Err(FromVariantError::BadType {
626            expected: to_type,
627            actual: from_type,
628        }
629        .into_error(variant.clone()));
630    }
631
632    // Find correct from->to conversion constructor.
633    let converter = unsafe {
634        let get_constructor = interface_fn!(get_variant_to_type_constructor);
635        get_constructor(to_type.sys())
636    };
637
638    // Must be available, since we checked with `variant_can_convert_strict`.
639    let converter =
640        converter.unwrap_or_else(|| panic!("missing converter for {from_type:?} -> {to_type:?}"));
641
642    // Perform actual conversion on the FFI types. The GDExtension conversion constructor only works with types supported
643    // by Godot (i.e. GodotType), not GodotConvert (like i8).
644    let ffi_result = unsafe {
645        <<T::Via as GodotType>::Ffi as GodotFfi>::new_with_uninit(|result_ptr| {
646            converter(result_ptr, sys::SysPtr::force_mut(variant.var_sys()));
647        })
648    };
649
650    // Try to convert the FFI types back to the user type. Can still fail, e.g. i64 -> i8.
651    let via = <T::Via as GodotType>::try_from_ffi(ffi_result)?;
652    let concrete = T::try_from_godot(via)?;
653
654    Ok(concrete)
655}
656
657fn can_convert_godot_strict(from_type: VariantType, to_type: VariantType) -> bool {
658    // Godot "strict" conversion is still quite permissive.
659    // See Variant::can_convert_strict() in C++, https://github.com/godotengine/godot/blob/master/core/variant/variant.cpp#L532-L532.
660    unsafe {
661        let can_convert_fn = interface_fn!(variant_can_convert_strict);
662        can_convert_fn(from_type.sys(), to_type.sys()) == sys::conv::SYS_TRUE
663    }
664}