godot_core/builtin/collections/
array_functional_ops.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 crate::builtin::{to_usize, Array, Callable, Variant, VariantArray};
9use crate::meta::{ArrayElement, AsArg};
10use crate::{meta, sys};
11
12/// Immutable, functional-programming operations for `Array`, based on Godot callables.
13///
14/// Returned by [`Array::functional_ops()`].
15///
16/// These methods exist to provide parity with Godot, e.g. when porting GDScript code to Rust. However, they come with several disadvantages
17/// compared to Rust's [iterator adapters](https://doc.rust-lang.org/stable/core/iter/index.html#adapters):
18/// - Not type-safe: callables are dynamically typed, so you need to double-check signatures. Godot may misinterpret returned values
19///   (e.g. predicates apply to any "truthy" values, not just booleans).
20/// - Slower: dispatching through callables is typically more costly than iterating over variants, especially since every call involves multiple
21///   variant conversions, too. Combining multiple operations like `filter().map()` is very expensive due to intermediate allocations.
22/// - Less composable/flexible: Godot's `map()` always returns an untyped array, even if the input is typed and unchanged by the mapping.
23///   Rust's `collect()` on the other hand gives you control over the output type. Chaining iterators can apply multiple transformations lazily.
24///
25/// In many cases, it is thus better to use [`Array::iter_shared()`] combined with iterator adapters. Check the individual method docs of
26/// this struct for concrete alternatives.
27pub struct ArrayFunctionalOps<'a, T: ArrayElement> {
28    array: &'a Array<T>,
29}
30
31impl<'a, T: ArrayElement> ArrayFunctionalOps<'a, T> {
32    pub(super) fn new(owner: &'a Array<T>) -> Self {
33        Self { array: owner }
34    }
35
36    /// Returns a new array containing only the elements for which the callable returns a truthy value.
37    ///
38    /// **Rust alternatives:** [`Iterator::filter()`].
39    ///
40    /// The callable has signature `fn(T) -> bool`.
41    ///
42    /// # Example
43    /// ```no_run
44    /// # use godot::prelude::*;
45    /// let array = array![1, 2, 3, 4, 5];
46    /// let even = array.functional_ops().filter(&Callable::from_fn("is_even", |args| {
47    ///     args[0].to::<i64>() % 2 == 0
48    /// }));
49    /// assert_eq!(even, array![2, 4]);
50    /// ```
51    #[must_use]
52    pub fn filter(&self, callable: &Callable) -> Array<T> {
53        // SAFETY: filter() returns array of same type as self.
54        unsafe { self.array.as_inner().filter(callable) }
55    }
56
57    /// Returns a new untyped array with each element transformed by the callable.
58    ///
59    /// **Rust alternatives:** [`Iterator::map()`].
60    ///
61    /// The callable has signature `fn(T) -> Variant`. Since the transformation can change the element type, this method returns
62    /// a `VariantArray` (untyped array).
63    ///
64    /// # Example
65    /// ```no_run
66    /// # use godot::prelude::*;
67    /// let array = array![1.1, 1.5, 1.9];
68    /// let rounded = array.functional_ops().map(&Callable::from_fn("round", |args| {
69    ///     args[0].to::<f64>().round() as i64
70    /// }));
71    /// assert_eq!(rounded, varray![1, 2, 2]);
72    /// ```
73    #[must_use]
74    pub fn map(&self, callable: &Callable) -> VariantArray {
75        // SAFETY: map() returns an untyped array.
76        unsafe { self.array.as_inner().map(callable) }
77    }
78
79    /// Reduces the array to a single value by iteratively applying the callable.
80    ///
81    /// **Rust alternatives:** [`Iterator::fold()`] or [`Iterator::reduce()`].
82    ///
83    /// The callable takes two arguments: the accumulator and the current element.
84    /// It returns the new accumulator value. The process starts with `initial` as the accumulator.
85    ///
86    /// # Example
87    /// ```no_run
88    /// # use godot::prelude::*;
89    /// let array = array![1, 2, 3, 4];
90    /// let sum = array.functional_ops().reduce(
91    ///     &Callable::from_fn("sum", |args| {
92    ///         args[0].to::<i64>() + args[1].to::<i64>()
93    ///     }),
94    ///     &0.to_variant()
95    /// );
96    /// assert_eq!(sum, 10.to_variant());
97    /// ```
98    #[must_use]
99    pub fn reduce(&self, callable: &Callable, initial: &Variant) -> Variant {
100        self.array.as_inner().reduce(callable, initial)
101    }
102
103    /// Returns `true` if the callable returns a truthy value for at least one element.
104    ///
105    /// **Rust alternatives:** [`Iterator::any()`].
106    ///
107    /// The callable has signature `fn(element) -> bool`.
108    ///
109    /// # Example
110    /// ```no_run
111    /// # use godot::prelude::*;
112    /// let array = array![1, 2, 3, 4];
113    /// let any_even = array.functional_ops().any(&Callable::from_fn("is_even", |args| {
114    ///     args[0].to::<i64>() % 2 == 0
115    /// }));
116    /// assert!(any_even);
117    /// ```
118    pub fn any(&self, callable: &Callable) -> bool {
119        self.array.as_inner().any(callable)
120    }
121
122    /// Returns `true` if the callable returns a truthy value for all elements.
123    ///
124    /// **Rust alternatives:** [`Iterator::all()`].
125    ///
126    /// The callable has signature `fn(element) -> bool`.
127    ///
128    /// # Example
129    /// ```no_run
130    /// # use godot::prelude::*;
131    /// let array = array![2, 4, 6];
132    /// let all_even = array.functional_ops().all(&Callable::from_fn("is_even", |args| {
133    ///     args[0].to::<i64>() % 2 == 0
134    /// }));
135    /// assert!(all_even);
136    /// ```
137    pub fn all(&self, callable: &Callable) -> bool {
138        self.array.as_inner().all(callable)
139    }
140
141    /// Finds the index of the first element matching a custom predicate.
142    ///
143    /// **Rust alternatives:** [`Iterator::position()`].
144    ///
145    /// The callable has signature `fn(element) -> bool`.
146    ///
147    /// Returns the index of the first element for which the callable returns a truthy value, starting from `from`.
148    /// If no element matches, returns `None`.
149    ///
150    /// # Example
151    /// ```no_run
152    /// # use godot::prelude::*;
153    /// let array = array![1, 2, 3, 4, 5];
154    /// let is_even = Callable::from_fn("is_even", |args| {
155    ///     args[0].to::<i64>() % 2 == 0
156    /// });
157    /// assert_eq!(array.functional_ops().find_custom(&is_even, None), Some(1)); // value 2
158    /// assert_eq!(array.functional_ops().find_custom(&is_even, Some(2)), Some(3)); // value 4
159    /// ```
160    #[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
161    pub fn find_custom(&self, callable: &Callable, from: Option<usize>) -> Option<usize> {
162        let from = from.map(|i| i as i64).unwrap_or(0);
163        let found_index = self.array.as_inner().find_custom(callable, from);
164
165        sys::found_to_option(found_index)
166    }
167
168    /// Finds the index of the last element matching a custom predicate, searching backwards.
169    ///
170    /// **Rust alternatives:** [`Iterator::rposition()`].
171    ///
172    /// The callable has signature `fn(element) -> bool`.
173    ///
174    /// Returns the index of the last element for which the callable returns a truthy value, searching backwards from `from`.
175    /// If no element matches, returns `None`.
176    ///
177    /// # Example
178    /// ```no_run
179    /// # use godot::prelude::*;
180    /// let array = array![1, 2, 3, 4, 5];
181    /// let is_even = Callable::from_fn("is_even", |args| {
182    ///     args[0].to::<i64>() % 2 == 0
183    /// });
184    /// assert_eq!(array.functional_ops().rfind_custom(&is_even, None), Some(3)); // value 4
185    /// assert_eq!(array.functional_ops().rfind_custom(&is_even, Some(2)), Some(1)); // value 2
186    /// ```
187    #[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
188    pub fn rfind_custom(&self, callable: &Callable, from: Option<usize>) -> Option<usize> {
189        let from = from.map(|i| i as i64).unwrap_or(-1);
190        let found_index = self.array.as_inner().rfind_custom(callable, from);
191
192        sys::found_to_option(found_index)
193    }
194
195    /// Finds the index of a value in a sorted array using binary search, with `Callable` custom predicate.
196    ///
197    /// The callable `pred` takes two elements `(a, b)` and should return if `a < b` (strictly less).
198    /// For a type-safe version, check out [`Array::bsearch_by()`].
199    ///
200    /// If the value is not present in the array, returns the insertion index that would maintain sorting order.
201    ///
202    /// Calling `bsearch_custom()` on an unsorted array results in unspecified behavior. Consider using [`Array::sort_unstable_custom()`]
203    /// to ensure the sorting order is compatible with your callable's ordering.
204    pub fn bsearch_custom(&self, value: impl AsArg<T>, pred: &Callable) -> usize {
205        meta::arg_into_ref!(value: T);
206
207        to_usize(
208            self.array
209                .as_inner()
210                .bsearch_custom(&value.to_variant(), pred, true),
211        )
212    }
213}