Skip to main content

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::{Array, Callable, VarArray, Variant, to_usize};
9use crate::meta::{AsArg, Element};
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: Element> {
28    array: &'a Array<T>,
29}
30
31impl<'a, T: Element> 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 = iarray![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        self.array.as_inner().filter(callable).cast_array::<T>()
54    }
55
56    /// Returns a new untyped array with each element transformed by the callable.
57    ///
58    /// **Rust alternatives:** [`Iterator::map()`].
59    ///
60    /// The callable has signature `fn(T) -> Variant`. Since the transformation can change the element type, this method returns
61    /// a `VarArray` (untyped array).
62    ///
63    /// # Example
64    /// ```no_run
65    /// # use godot::prelude::*;
66    /// let array = iarray![1.1, 1.5, 1.9];
67    /// let rounded = array.functional_ops().map(&Callable::from_fn("round", |args| {
68    ///     args[0].to::<f64>().round() as i64
69    /// }));
70    /// assert_eq!(rounded, varray![1, 2, 2]);
71    /// ```
72    #[must_use]
73    pub fn map(&self, callable: &Callable) -> VarArray {
74        self.array.as_inner().map(callable).cast_array()
75    }
76
77    /// Reduces the array to a single value by iteratively applying the callable.
78    ///
79    /// **Rust alternatives:** [`Iterator::fold()`] or [`Iterator::reduce()`].
80    ///
81    /// The callable takes two arguments: the accumulator and the current element.
82    /// It returns the new accumulator value. The process starts with `initial` as the accumulator.
83    ///
84    /// # Example
85    /// ```no_run
86    /// # use godot::prelude::*;
87    /// let array = iarray![1, 2, 3, 4];
88    /// let sum = array.functional_ops().reduce(
89    ///     &Callable::from_fn("sum", |args| {
90    ///         args[0].to::<i64>() + args[1].to::<i64>()
91    ///     }),
92    ///     &0.to_variant()
93    /// );
94    /// assert_eq!(sum, 10.to_variant());
95    /// ```
96    #[must_use]
97    pub fn reduce(&self, callable: &Callable, initial: &Variant) -> Variant {
98        self.array.as_inner().reduce(callable, initial)
99    }
100
101    /// Returns `true` if the callable returns a truthy value for at least one element.
102    ///
103    /// **Rust alternatives:** [`Iterator::any()`].
104    ///
105    /// The callable has signature `fn(element) -> bool`.
106    ///
107    /// # Example
108    /// ```no_run
109    /// # use godot::prelude::*;
110    /// let array = iarray![1, 2, 3, 4];
111    /// let any_even = array.functional_ops().any(&Callable::from_fn("is_even", |args| {
112    ///     args[0].to::<i64>() % 2 == 0
113    /// }));
114    /// assert!(any_even);
115    /// ```
116    pub fn any(&self, callable: &Callable) -> bool {
117        self.array.as_inner().any(callable)
118    }
119
120    /// Returns `true` if the callable returns a truthy value for all elements.
121    ///
122    /// **Rust alternatives:** [`Iterator::all()`].
123    ///
124    /// The callable has signature `fn(element) -> bool`.
125    ///
126    /// # Example
127    /// ```no_run
128    /// # use godot::prelude::*;
129    /// let array = iarray![2, 4, 6];
130    /// let all_even = array.functional_ops().all(&Callable::from_fn("is_even", |args| {
131    ///     args[0].to::<i64>() % 2 == 0
132    /// }));
133    /// assert!(all_even);
134    /// ```
135    pub fn all(&self, callable: &Callable) -> bool {
136        self.array.as_inner().all(callable)
137    }
138
139    /// Finds the index of the first element matching a custom predicate.
140    ///
141    /// **Rust alternatives:** [`Iterator::position()`].
142    ///
143    /// The callable has signature `fn(element) -> bool`.
144    ///
145    /// Returns the index of the first element for which the callable returns a truthy value, starting from `from`.
146    /// If no element matches, returns `None`.
147    ///
148    /// # Example
149    /// ```no_run
150    /// # use godot::prelude::*;
151    /// let array = iarray![1, 2, 3, 4, 5];
152    /// let is_even = Callable::from_fn("is_even", |args| {
153    ///     args[0].to::<i64>() % 2 == 0
154    /// });
155    /// assert_eq!(array.functional_ops().find_custom(&is_even, None), Some(1)); // value 2
156    /// assert_eq!(array.functional_ops().find_custom(&is_even, Some(2)), Some(3)); // value 4
157    /// ```
158    #[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
159    pub fn find_custom(&self, callable: &Callable, from: Option<usize>) -> Option<usize> {
160        let from = from.map(|i| i as i64).unwrap_or(0);
161        let found_index = self.array.as_inner().find_custom(callable, from);
162
163        sys::found_to_option(found_index)
164    }
165
166    /// Finds the index of the last element matching a custom predicate, searching backwards.
167    ///
168    /// **Rust alternatives:** [`Iterator::rposition()`].
169    ///
170    /// The callable has signature `fn(element) -> bool`.
171    ///
172    /// Returns the index of the last element for which the callable returns a truthy value, searching backwards from `from`.
173    /// If no element matches, returns `None`.
174    ///
175    /// # Example
176    /// ```no_run
177    /// # use godot::prelude::*;
178    /// let array = iarray![1, 2, 3, 4, 5];
179    /// let is_even = Callable::from_fn("is_even", |args| {
180    ///     args[0].to::<i64>() % 2 == 0
181    /// });
182    /// assert_eq!(array.functional_ops().rfind_custom(&is_even, None), Some(3)); // value 4
183    /// assert_eq!(array.functional_ops().rfind_custom(&is_even, Some(2)), Some(1)); // value 2
184    /// ```
185    #[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
186    pub fn rfind_custom(&self, callable: &Callable, from: Option<usize>) -> Option<usize> {
187        let from = from.map(|i| i as i64).unwrap_or(-1);
188        let found_index = self.array.as_inner().rfind_custom(callable, from);
189
190        sys::found_to_option(found_index)
191    }
192
193    /// Finds the index of a value in a sorted array using binary search, with `Callable` custom predicate.
194    ///
195    /// The callable `pred` takes two elements `(a, b)` and should return if `a < b` (strictly less).
196    /// For a type-safe version, check out [`Array::bsearch_by()`].
197    ///
198    /// If the value is not present in the array, returns the insertion index that would maintain sorting order.
199    ///
200    /// Calling `bsearch_custom()` on an unsorted array results in unspecified behavior. Consider using
201    /// [`AnyArray::sort_unstable_custom()`][crate::builtin::AnyArray::sort_unstable_custom()] beforehand.
202    /// to ensure the sorting order is compatible with your callable's ordering.
203    pub fn bsearch_custom(&self, value: impl AsArg<T>, pred: &Callable) -> usize {
204        meta::arg_into_ref!(value: T);
205
206        to_usize(
207            self.array
208                .as_inner()
209                .bsearch_custom(&value.to_variant(), pred, true),
210        )
211    }
212}