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}