Skip to content

Commit b4191da

Browse files
committed
Array<T> + AnyArray cast API consistency
Conversion methods between `Array<T>` and `AnyArray` values now follow the precedent of `Gd::try_cast()` + `Gd::upcast()`, meaning: * Upcast: `upcast_any_array` * Downcast: `cast_array<T>`, `cast_var_array` Also returns `self` instead of `ConvertError` on failure. This may need to be revisited in the future, it's also possible (but more expensive?) to return a pair of both.
1 parent 00344cf commit b4191da

File tree

8 files changed

+135
-55
lines changed

8 files changed

+135
-55
lines changed

godot-core/src/builtin/collections/any_array.rs

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use sys::{ffi_methods, GodotFfi};
1313
use crate::builtin::iter::ArrayFunctionalOps;
1414
use crate::builtin::*;
1515
use crate::meta;
16-
use crate::meta::error::{ArrayMismatch, ConvertError, ErrorKind};
16+
use crate::meta::error::ConvertError;
1717
use crate::meta::{
1818
ArrayElement, ElementType, FromGodot, GodotConvert, GodotFfiVariant, GodotType, ToGodot,
1919
};
@@ -38,15 +38,8 @@ use crate::meta::{
3838
///
3939
/// `AnyArray` does not provide any operations where data flows _in_ to the array, such as `push()` or `insert()`.
4040
///
41-
/// # Conversions
42-
/// Engine APIs accept `&AnyArray` parameters. Due to [deref coercion], you can pass any `&Array<T>` implicitly, even for `T=Variant`.
43-
///
44-
/// If you need to upcast `Array<T>` _values_ to `AnyArray`, use [`Array::into_any()`].
45-
///
46-
/// [deref coercion]: struct.Array.html#deref-methods-AnyArray
47-
///
48-
/// You can explicitly downcast an `AnyArray` using [`try_into_typed()`][Self::try_into_typed] and
49-
/// [`try_into_untyped()`][Self::try_into_untyped].
41+
/// ## Conversions
42+
/// See the [corresponding section in `Array`](struct.Array.html#conversions-between-arrays).
5043
#[derive(PartialEq, PartialOrd)]
5144
#[repr(transparent)] // Guarantees same layout as VarArray, enabling Deref from Array<T>.
5245
pub struct AnyArray {
@@ -215,7 +208,7 @@ impl AnyArray {
215208
/// To create a deep copy, use [`duplicate_deep()`][Self::duplicate_deep] instead.
216209
/// To create a new reference to the same array data, use [`clone()`][Clone::clone].
217210
pub fn duplicate_shallow(&self) -> AnyArray {
218-
self.array.duplicate_shallow().into_any()
211+
self.array.duplicate_shallow().upcast_any_array()
219212
}
220213

221214
/// Returns a deep copy, duplicating nested `Array`/`Dictionary` elements but keeping `Object` elements shared.
@@ -225,7 +218,7 @@ impl AnyArray {
225218
/// To create a shallow copy, use [`duplicate_shallow()`][Self::duplicate_shallow] instead.
226219
/// To create a new reference to the same array data, use [`clone()`][Clone::clone].
227220
pub fn duplicate_deep(&self) -> Self {
228-
self.array.duplicate_deep().into_any()
221+
self.array.duplicate_deep().upcast_any_array()
229222
}
230223

231224
/// Returns a sub-range as a new array.
@@ -238,7 +231,7 @@ impl AnyArray {
238231
#[doc(alias = "slice")]
239232
pub fn subarray_shallow(&self, range: impl meta::SignedRange, step: Option<i32>) -> Self {
240233
let sliced = self.array.subarray_shallow(range, step);
241-
sliced.into_any()
234+
sliced.upcast_any_array()
242235
}
243236

244237
/// Returns a sub-range as a new `Array`.
@@ -251,7 +244,7 @@ impl AnyArray {
251244
#[doc(alias = "slice")]
252245
pub fn subarray_deep(&self, range: impl meta::SignedRange, step: Option<i32>) -> Self {
253246
let sliced = self.array.subarray_deep(range, step);
254-
sliced.into_any()
247+
sliced.upcast_any_array()
255248
}
256249

257250
/// Returns an non-exclusive iterator over the elements of the `Array`.
@@ -407,9 +400,15 @@ impl AnyArray {
407400

408401
/// Converts to `Array<T>` if the runtime type matches.
409402
///
410-
/// Returns `Err` if the array's dynamic type differs from `T`. Check [`element_type()`][Self::element_type]
403+
/// If `T=Variant`, this will attempt to "downcast" to an untyped array, identical to [`try_cast_var_array()`][Self::try_cast_var_array].
404+
///
405+
/// Returns `Err(self)` if the array's dynamic type differs from `T`. Check [`element_type()`][Self::element_type]
411406
/// before calling to determine what type the array actually holds.
412-
pub fn try_into_typed<T: ArrayElement>(self) -> Result<Array<T>, ConvertError> {
407+
///
408+
/// Consumes `self`, to avoid incrementing reference-count. Use `clone()` if you need to keep the original. Using `self` also has the nice
409+
/// side effect that this method cannot be called on concrete `Array<T>` types, as `Deref` only operates on references, not values.
410+
// Naming: not `try_into_typed` because T can be Variant.
411+
pub fn try_cast_array<T: ArrayElement>(self) -> Result<Array<T>, Self> {
413412
let from_type = self.array.element_type();
414413
let to_type = ElementType::of::<T>();
415414

@@ -418,19 +417,22 @@ impl AnyArray {
418417
let array = unsafe { self.array.assume_type::<T>() };
419418
Ok(array)
420419
} else {
421-
let mismatch = ArrayMismatch {
422-
expected: to_type,
423-
actual: from_type,
424-
};
425-
Err(ConvertError::with_kind(ErrorKind::FromOutArray(mismatch)))
420+
// If we add ConvertError here:
421+
// let mismatch = ArrayMismatch { expected: to_type, actual: from_type };
422+
// Err(ConvertError::with_kind(ErrorKind::FromAnyArray(mismatch)))
423+
424+
Err(self)
426425
}
427426
}
428427

429-
/// Converts to `VarArray` if the array is untyped.
428+
/// Converts to an untyped `VarArray` if the array is untyped.
429+
///
430+
/// This is a shorthand for [`try_cast_array::<Variant>()`][Self::try_cast_array].
430431
///
431-
/// This is a shorthand for [`try_into_typed::<Variant>()`][Self::try_into_typed].
432-
pub fn try_into_untyped(self) -> Result<VarArray, ConvertError> {
433-
self.try_into_typed::<Variant>()
432+
/// Consumes `self`, to avoid incrementing reference-count. Use `clone()` if you need to keep the original. Using `self` also has the nice
433+
/// side effect that this method cannot be called on concrete `Array<T>` types, as `Deref` only operates on references, not values.
434+
pub fn try_cast_var_array(self) -> Result<VarArray, Self> {
435+
self.try_cast_array::<Variant>()
434436
}
435437

436438
// If we add direct-conversion methods that panic, we can use meta::element_godot_type_name::<T>() to mention type in case of mismatch.

godot-core/src/builtin/collections/array.rs

Lines changed: 60 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ use crate::registry::property::{BuiltinExport, Export, Var};
3333
/// Check out the [book](https://godot-rust.github.io/book/godot-api/builtins.html#arrays-and-dictionaries) for a tutorial on arrays.
3434
///
3535
/// # Reference semantics
36-
///
3736
/// Like in GDScript, `Array` acts as a reference type: multiple `Array` instances may
3837
/// refer to the same underlying array, and changes to one are visible in the other.
3938
///
@@ -42,7 +41,6 @@ use crate::registry::property::{BuiltinExport, Export, Var};
4241
/// or [`duplicate_deep()`][Self::duplicate_deep].
4342
///
4443
/// # Element type
45-
///
4644
/// Godot's `Array` builtin can be either typed or untyped. In Rust, this maps to three representations:
4745
///
4846
/// - Untyped array: `VariantArray`, a type alias for `Array<Variant>`. \
@@ -55,14 +53,29 @@ use crate::registry::property::{BuiltinExport, Export, Var};
5553
/// Represents either typed or untyped arrays. This is different from `Array<Variant>`, because the latter allows you to insert
5654
/// `Variant` objects. However, for an array that has dynamic type `i64`, it is not allowed to insert any variants that are not `i64`.
5755
///
58-
/// Godot APIs reflect this. You can `Deref`-coerce from any `Array<T>` to `AnyArray` (e.g. when passing arguments), and explicitly
59-
/// convert the other way, using [`AnyArray::try_into_typed()`] and [`AnyArray::try_into_untyped()`].
60-
///
6156
/// If you plan to use any integer or float types apart from `i64` and `f64`, read
6257
/// [this documentation](../meta/trait.ArrayElement.html#integer-and-float-types).
6358
///
64-
/// ## Typed array example
59+
/// ## Conversions between arrays
60+
/// The following table gives an overview of useful conversions.
61+
///
62+
/// | From | To | Conversion method |
63+
/// |--------------------|------------------|----------------------------------------------------------|
64+
/// | `AnyArray` | `Array<T>` | [`AnyArray::try_cast_array::<T>`] |
65+
/// | `AnyArray` | `Array<Variant>` | [`AnyArray::try_cast_var_array`] |
66+
/// | `&Array<T>` | `&AnyArray` | Implicit via [deref coercion]; often used in class APIs. |
67+
/// | `Array<T>` | `AnyArray` | [`Array<T>::upcast_any_array`] |
68+
/// | `Array<T>` | `PackedArray<T>` | [`Array<T>::to_packed_array`] |
69+
/// | `PackedArray<T>` | `Array<T>` | [`PackedArray<T>::to_typed_array`] |
70+
/// | `PackedArray<T>` | `Array<Variant>` | [`PackedArray<T>::to_var_array`] |
6571
///
72+
/// Note that it's **not** possible to upcast `Array<Gd<Derived>>` to `Array<Gd<Base>>`. `Array<T>` is not covariant over the `T` parameter,
73+
/// otherwise it would be possible to insert `Base` pointers that aren't actually `Derived`.
74+
// Note: the above could theoretically be implemented by making AnyArray<T> generic and covariant over T.
75+
///
76+
/// [deref coercion]: struct.Array.html#deref-methods-AnyArray
77+
///
78+
/// ## Typed array example
6679
/// ```no_run
6780
/// # use godot::prelude::*;
6881
/// // Create typed Array<i64> and add values.
@@ -95,7 +108,6 @@ use crate::registry::property::{BuiltinExport, Export, Var};
95108
/// ```
96109
///
97110
/// ## Untyped array example
98-
///
99111
/// ```no_run
100112
/// # use godot::prelude::*;
101113
/// // VarArray allows dynamic element types.
@@ -116,7 +128,6 @@ use crate::registry::property::{BuiltinExport, Export, Var};
116128
/// ```
117129
///
118130
/// # Working with signed ranges and steps
119-
///
120131
/// For negative indices, use [`wrapped()`](crate::meta::wrapped).
121132
///
122133
/// ```no_run
@@ -145,14 +156,12 @@ use crate::registry::property::{BuiltinExport, Export, Var};
145156
/// ```
146157
///
147158
/// # Thread safety
148-
///
149159
/// Usage is safe if the `Array` is used on a single thread only. Concurrent reads on
150160
/// different threads are also safe, but any writes must be externally synchronized. The Rust
151161
/// compiler will enforce this as long as you use only Rust threads, but it cannot protect against
152162
/// concurrent modification on other threads (e.g. created through GDScript).
153163
///
154164
/// # Element type safety
155-
///
156165
/// We provide a richer set of element types than Godot, for convenience and stronger invariants in your _Rust_ code.
157166
/// This, however, means that the Godot representation of such arrays is not capable of incorporating the additional "Rust-side" information.
158167
/// This can lead to situations where GDScript code or the editor UI can insert values that do not fulfill the Rust-side invariants.
@@ -168,11 +177,9 @@ use crate::registry::property::{BuiltinExport, Export, Var};
168177
/// Godot doesn't know Rust traits and will only see the `T` part.
169178
///
170179
/// # Differences from GDScript
171-
///
172180
/// Unlike GDScript, all indices and sizes are unsigned, so negative indices are not supported.
173181
///
174182
/// # Godot docs
175-
///
176183
/// [`Array[T]` (stable)](https://docs.godotengine.org/en/stable/classes/class_array.html)
177184
pub struct Array<T: ArrayElement> {
178185
// Safety Invariant: The type of all values in `opaque` matches the type `T`.
@@ -761,7 +768,7 @@ impl<T: ArrayElement> Array<T> {
761768
///
762769
/// Typically, you can use deref coercion to convert `&Array<T>` to `&AnyArray`. This method is useful if you need `AnyArray` by value.
763770
/// It consumes `self` to avoid incrementing the reference count; use `clone()` if you use the original array further.
764-
pub fn into_any(self) -> AnyArray {
771+
pub fn upcast_any_array(self) -> AnyArray {
765772
AnyArray::from_typed_or_untyped(self)
766773
}
767774

@@ -890,6 +897,7 @@ impl<T: ArrayElement> Array<T> {
890897
/// Note also that any `GodotType` can be written to a `Variant` array.
891898
///
892899
/// In the current implementation, both cases will produce a panic rather than undefined behavior, but this should not be relied upon.
900+
#[cfg(safeguards_strict)]
893901
unsafe fn assume_type_ref<U: ArrayElement>(&self) -> &Array<U> {
894902
// The memory layout of `Array<T>` does not depend on `T`.
895903
std::mem::transmute::<&Array<T>, &Array<U>>(self)
@@ -1088,12 +1096,6 @@ impl<T: ArrayElement> Array<T> {
10881096
}
10891097
}
10901098

1091-
#[test]
1092-
fn correct_variant_t() {
1093-
assert!(Array::<Variant>::has_variant_t());
1094-
assert!(!Array::<i64>::has_variant_t());
1095-
}
1096-
10971099
impl VarArray {
10981100
/// # Safety
10991101
/// - Variant must have type `VariantType::ARRAY`.
@@ -1371,6 +1373,9 @@ impl<T: ArrayElement> GodotFfiVariant for Array<T> {
13711373
// ----------------------------------------------------------------------------------------------------------------------------------------------
13721374
// Conversion traits
13731375

1376+
// From impls converting values (like [T; N] or Vec<T>) are explicitly not supported, because there's no perf benefit in submitting ownership.
1377+
// This is different for PackedArray, which can move values. See also related: https://github.com/godot-rust/gdext/pull/1286.
1378+
13741379
/// Creates a `Array` from the given Rust array.
13751380
impl<T: ArrayElement + ToGodot, const N: usize> From<&[T; N]> for Array<T> {
13761381
fn from(arr: &[T; N]) -> Self {
@@ -1444,6 +1449,7 @@ impl<T: ArrayElement + FromGodot> From<&Array<T>> for Vec<T> {
14441449
}
14451450

14461451
// ----------------------------------------------------------------------------------------------------------------------------------------------
1452+
// Iterators
14471453

14481454
/// An iterator over typed elements of an [`Array`].
14491455
pub struct Iter<'a, T: ArrayElement> {
@@ -1515,6 +1521,7 @@ impl<T: ArrayElement> PartialOrd for Array<T> {
15151521
}
15161522

15171523
// ----------------------------------------------------------------------------------------------------------------------------------------------
1524+
// Expression macros
15181525

15191526
/// Constructs [`Array`] literals, similar to Rust's standard `vec!` macro.
15201527
///
@@ -1634,6 +1641,7 @@ macro_rules! vslice {
16341641
}
16351642

16361643
// ----------------------------------------------------------------------------------------------------------------------------------------------
1644+
// Serde support
16371645

16381646
#[cfg(feature = "serde")]
16391647
mod serialize {
@@ -1704,3 +1712,36 @@ mod serialize {
17041712
}
17051713
}
17061714
}
1715+
1716+
// ----------------------------------------------------------------------------------------------------------------------------------------------
1717+
// Tests
1718+
1719+
/// Verifies that `AnyArray::try_cast*()` cannot be called on concrete arrays.
1720+
///
1721+
/// > error[E0507]: cannot move out of dereference of `godot::prelude::Array<i64>`
1722+
///
1723+
/// ```compile_fail
1724+
/// use godot::prelude::*;
1725+
/// let a: Array<i64> = array![1, 2, 3];
1726+
/// a.try_cast_var_array();
1727+
/// ```
1728+
///
1729+
/// ```compile_fail
1730+
/// use godot::prelude::*;
1731+
/// let a: VarArray = varray![1, 2, 3];
1732+
/// a.try_cast_array::<i64>();
1733+
/// ```
1734+
///
1735+
/// This works:
1736+
/// ```no_run
1737+
/// use godot::prelude::*;
1738+
/// let a: VarArray = varray![1, 2, 3];
1739+
/// a.upcast_any_array().try_cast_array::<i64>();
1740+
/// ```
1741+
fn __cannot_downcast_from_concrete() {}
1742+
1743+
#[test]
1744+
fn correct_variant_t() {
1745+
assert!(Array::<Variant>::has_variant_t());
1746+
assert!(!Array::<i64>::has_variant_t());
1747+
}

godot-core/src/builtin/collections/packed_array.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ pub type PackedColorArray = PackedArray<Color>;
8787
/// but any writes must be externally synchronized. The Rust compiler will enforce this as
8888
/// long as you use only Rust threads, but it cannot protect against concurrent modification
8989
/// on other threads (e.g. created through GDScript).
90+
///
91+
/// # Element type and conversions
92+
/// See the [corresponding section in `Array`](struct.Array.html#conversions-between-arrays).
9093
pub struct PackedArray<T: PackedArrayElement> {
9194
// All packed arrays have same memory layout.
9295
opaque: sys::types::OpaquePackedByteArray,

godot-core/src/meta/error/convert_error.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ impl ConvertError {
3636
}
3737

3838
/// Create a new custom error for a conversion, without associated value.
39+
#[allow(dead_code)] // Needed a few times already, stays to prevent churn on refactorings.
3940
pub(crate) fn with_kind(kind: ErrorKind) -> Self {
4041
Self { kind, value: None }
4142
}
@@ -162,7 +163,7 @@ pub(crate) enum ErrorKind {
162163
FromGodot(FromGodotError),
163164
FromFfi(FromFfiError),
164165
FromVariant(FromVariantError),
165-
FromOutArray(ArrayMismatch),
166+
// FromAnyArray(ArrayMismatch), -- needed if AnyArray downcasts return ConvertError one day.
166167
Custom(Option<Cause>),
167168
}
168169

@@ -172,7 +173,6 @@ impl fmt::Display for ErrorKind {
172173
Self::FromGodot(from_godot) => write!(f, "{from_godot}"),
173174
Self::FromVariant(from_variant) => write!(f, "{from_variant}"),
174175
Self::FromFfi(from_ffi) => write!(f, "{from_ffi}"),
175-
Self::FromOutArray(array_mismatch) => write!(f, "{array_mismatch}"),
176176
Self::Custom(cause) => match cause {
177177
Some(c) => write!(f, "{c}"),
178178
None => write!(f, "custom error"),

godot-core/src/obj/traits.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -773,7 +773,7 @@ pub mod cap {
773773
use super::*;
774774
use crate::builtin::{StringName, Variant};
775775
use crate::meta::PropertyInfo;
776-
use crate::obj::{Base, Bounds, Gd};
776+
use crate::obj::{Base, Gd};
777777
use crate::storage::{IntoVirtualMethodReceiver, VirtualMethodReceiver};
778778

779779
/// Trait for all classes that are default-constructible from the Godot engine.

0 commit comments

Comments
 (0)