Skip to main content

tuwunel_core/utils/future/
bool_ext.rs

1//! Short-circuiting Boolean combinators over concurrently polled futures.
2//!
3//! The fixed-arity forms poll their inputs in the order given, and a pass ends
4//! at the first input to decide the result, leaving those after it unpolled.
5//! An unpolled input runs none of the work its future defers to `poll`, which
6//! for a database read is the pool dispatch and the storage I/O. The elision
7//! needs the deciding input ready on its first poll, since a pending one does
8//! not end the pass. Order the arguments cheapest and likeliest to decide
9//! first; the iterator forms promise no such order.
10
11#![expect(clippy::many_single_char_names, clippy::impl_trait_in_params)]
12
13use futures::{
14	FutureExt,
15	future::{select_ok, try_join, try_join_all, try_join3, try_join4},
16};
17
18use crate::utils::BoolExt as _;
19
20/// Combines Boolean futures with concurrent short-circuit logic.
21///
22/// Conjunction resolves false on the first false output and true only after
23/// every input resolves true. Disjunction resolves true on the first true
24/// output and false only after every input resolves false.
25pub trait BoolExt
26where
27	Self: Future<Output = bool> + Send,
28{
29	/// Computes the disjunction of two Boolean futures.
30	///
31	/// Both futures are polled concurrently. The returned future resolves true
32	/// on the first true output or false after both produce false. Unlike the
33	/// conjunctions, this collects its inputs into a heap `Vec`, so a chain of
34	/// `or` allocates once per link.
35	fn or<B>(self, b: B) -> impl Future<Output = bool> + Send
36	where
37		B: Future<Output = bool> + Send + Unpin,
38		Self: Sized + Unpin;
39
40	/// Computes the conjunction of two Boolean futures.
41	///
42	/// Both futures are polled concurrently. The returned future resolves false
43	/// on the first false output or true after both produce true.
44	fn and<B>(self, b: B) -> impl Future<Output = bool> + Send
45	where
46		B: Future<Output = bool> + Send,
47		Self: Sized;
48
49	/// Computes the conjunction of three Boolean futures.
50	///
51	/// The receiver and both arguments are polled concurrently. The result is
52	/// true only when all three futures produce true.
53	fn and2<B, C>(self, b: B, c: C) -> impl Future<Output = bool> + Send
54	where
55		B: Future<Output = bool> + Send,
56		C: Future<Output = bool> + Send,
57		Self: Sized;
58
59	/// Computes the conjunction of four Boolean futures.
60	///
61	/// The receiver and all three arguments are polled concurrently. The result
62	/// is true only when every future produces true.
63	fn and3<B, C, D>(self, b: B, c: C, d: D) -> impl Future<Output = bool> + Send
64	where
65		B: Future<Output = bool> + Send,
66		C: Future<Output = bool> + Send,
67		D: Future<Output = bool> + Send,
68		Self: Sized;
69}
70
71impl<Fut> BoolExt for Fut
72where
73	Fut: Future<Output = bool> + Send,
74{
75	fn or<B>(self, b: B) -> impl Future<Output = bool> + Send
76	where
77		B: Future<Output = bool> + Send + Unpin,
78		Self: Sized + Unpin,
79	{
80		select_ok([self.map(test).left_future(), b.map(test).right_future()])
81			.map(|res| res.is_ok())
82	}
83
84	fn and<B>(self, b: B) -> impl Future<Output = bool> + Send
85	where
86		B: Future<Output = bool> + Send,
87		Self: Sized,
88	{
89		try_join(self.map(test), b.map(test)).map(|res| res.is_ok())
90	}
91
92	fn and2<B, C>(self, b: B, c: C) -> impl Future<Output = bool> + Send
93	where
94		B: Future<Output = bool> + Send,
95		C: Future<Output = bool> + Send,
96		Self: Sized,
97	{
98		try_join3(self.map(test), b.map(test), c.map(test)).map(|res| res.is_ok())
99	}
100
101	fn and3<B, C, D>(self, b: B, c: C, d: D) -> impl Future<Output = bool> + Send
102	where
103		B: Future<Output = bool> + Send,
104		C: Future<Output = bool> + Send,
105		D: Future<Output = bool> + Send,
106		Self: Sized,
107	{
108		try_join4(self.map(test), b.map(test), c.map(test), d.map(test)).map(|res| res.is_ok())
109	}
110}
111
112/// Computes the conjunction of an iterator of Boolean futures.
113///
114/// All inputs are polled concurrently and false short-circuits the operation.
115/// An empty iterator resolves to true. An exact upper size hint of thirty or
116/// fewer keeps a stack-resident boxed slice polled in order; anything else, an
117/// absent hint included, builds a `FuturesOrdered` polled by readiness.
118pub fn and<I, F>(args: I) -> impl Future<Output = bool> + Send
119where
120	I: Iterator<Item = F> + Send,
121	F: Future<Output = bool> + Send,
122{
123	let args = args.map(|a| a.map(test));
124
125	try_join_all(args).map(|res| res.is_ok())
126}
127
128/// Computes the disjunction of an iterator of Boolean futures.
129///
130/// All inputs are polled concurrently and true short-circuits the operation.
131/// False is returned only after every input resolves to false.
132///
133/// # Panics
134///
135/// Panics when the iterator contains no futures.
136pub fn or<I, F>(args: I) -> impl Future<Output = bool> + Send
137where
138	I: Iterator<Item = F> + Send,
139	F: Future<Output = bool> + Send + Unpin,
140{
141	let args = args.map(|a| a.map(test));
142
143	select_ok(args).map(|res| res.is_ok())
144}
145
146/// Computes the conjunction of four Boolean futures.
147///
148/// All four inputs are polled concurrently. The result is true only when every
149/// future resolves to true.
150pub fn and4(
151	a: impl Future<Output = bool> + Send,
152	b: impl Future<Output = bool> + Send,
153	c: impl Future<Output = bool> + Send,
154	d: impl Future<Output = bool> + Send,
155) -> impl Future<Output = bool> + Send {
156	a.and3(b, c, d)
157}
158
159/// Computes the conjunction of five Boolean futures.
160///
161/// All five inputs are polled concurrently. The result is true only when every
162/// future resolves to true.
163pub fn and5(
164	a: impl Future<Output = bool> + Send,
165	b: impl Future<Output = bool> + Send,
166	c: impl Future<Output = bool> + Send,
167	d: impl Future<Output = bool> + Send,
168	e: impl Future<Output = bool> + Send,
169) -> impl Future<Output = bool> + Send {
170	a.and2(b, c).and2(d, e)
171}
172
173/// Computes the conjunction of six Boolean futures.
174///
175/// All six inputs are polled concurrently. The result is true only when every
176/// future resolves to true.
177pub fn and6(
178	a: impl Future<Output = bool> + Send,
179	b: impl Future<Output = bool> + Send,
180	c: impl Future<Output = bool> + Send,
181	d: impl Future<Output = bool> + Send,
182	e: impl Future<Output = bool> + Send,
183	f: impl Future<Output = bool> + Send,
184) -> impl Future<Output = bool> + Send {
185	a.and3(b, c, d).and2(e, f)
186}
187
188/// Computes the conjunction of seven Boolean futures.
189///
190/// All seven inputs are polled concurrently. The result is true only when every
191/// future resolves to true.
192pub fn and7(
193	a: impl Future<Output = bool> + Send,
194	b: impl Future<Output = bool> + Send,
195	c: impl Future<Output = bool> + Send,
196	d: impl Future<Output = bool> + Send,
197	e: impl Future<Output = bool> + Send,
198	f: impl Future<Output = bool> + Send,
199	g: impl Future<Output = bool> + Send,
200) -> impl Future<Output = bool> + Send {
201	a.and3(b, c, d).and3(e, f, g)
202}
203
204fn test(test: bool) -> crate::Result<(), ()> { test.into_result() }