@@ -25,22 +25,129 @@ use std::sync::{Arc};
2525use std:: task:: { Context , Poll } ;
2626use parking_lot:: Mutex ;
2727
28+ /// Creates a [`Stream`] from an async generator function.
29+ ///
30+ /// The `generator` closure receives an [`Emitter<T>`] and runs as an async
31+ /// block. Each `emitter.emit(value).await` call suspends the generator and
32+ /// produces the next item in the stream. The stream ends when the generator
33+ /// future resolves.
34+ ///
35+ /// # Example
36+ ///
37+ /// ```
38+ /// use datafusion_execution::async_stream;
39+ /// use futures::StreamExt;
40+ ///
41+ /// # #[tokio::main(flavor = "current_thread")]
42+ /// # async fn main() {
43+ /// let stream = async_stream(|mut emitter| async move {
44+ /// for i in 0_i32..3 {
45+ /// emitter.emit(i).await;
46+ /// }
47+ /// });
48+ ///
49+ /// let values: Vec<i32> = stream.collect().await;
50+ /// assert_eq!(values, vec![0, 1, 2]);
51+ /// # }
52+ /// ```
53+ pub fn async_stream < T , F : Future < Output = ( ) > > (
54+ generator : impl FnOnce ( Emitter < T > ) -> F ,
55+ ) -> impl FusedStream < Item = T > {
56+ let ( emitter, receiver) = tx_rx ( ) ;
57+ AsyncStream :: new ( receiver, generator ( emitter) )
58+ }
59+
60+ /// Creates a fallible [`Stream`] from an async generator function.
61+ ///
62+ /// The `generator` closure receives a [`TryEmitter<T, E>`] and runs as an
63+ /// async block that returns `Result<(), E>`. Each `emitter.emit(value).await`
64+ /// call suspends the generator and produces `Ok(value)` as the next stream
65+ /// item. The `?` operator can be used inside the generator to short-circuit on
66+ /// errors: the error is emitted as the final `Err(e)` item and the stream
67+ /// ends. The stream also ends when the generator future resolves to `Ok(())`.
68+ ///
69+ /// # Example
70+ ///
71+ /// ```
72+ /// use datafusion_execution::async_try_stream;
73+ /// use futures::StreamExt;
74+ ///
75+ /// # #[tokio::main(flavor = "current_thread")]
76+ /// # async fn main() {
77+ /// let stream = async_try_stream(|mut emitter| async move {
78+ /// emitter.emit(1_i32).await;
79+ /// emitter.emit(2_i32).await;
80+ /// Err::<(), _>("something went wrong")?;
81+ /// emitter.emit(3_i32).await; // never reached
82+ /// Ok(())
83+ /// });
84+ ///
85+ /// let values: Vec<Result<i32, &str>> = stream.collect().await;
86+ /// assert_eq!(values, vec![Ok(1), Ok(2), Err("something went wrong")]);
87+ /// # }
88+ /// ```
89+ pub fn async_try_stream < T , E , F : Future < Output = Result < ( ) , E > > > (
90+ generator : impl FnOnce ( TryEmitter < T , E > ) -> F ,
91+ ) -> impl FusedStream < Item = Result < T , E > > {
92+ let ( try_emitter, mut emitter, receiver) = try_tx_rx :: < T , E > ( ) ;
93+ AsyncStream :: new ( receiver, async move {
94+ if let Err ( e) = generator ( try_emitter) . await {
95+ emitter. emit ( Err ( e) ) . await
96+ }
97+ } )
98+ }
99+
100+ /// Creates an `Emitter`/`Receiver` pair
101+ fn tx_rx < T > ( ) -> ( Emitter < T > , Receiver < T > ) {
102+ let slot = Arc :: new ( Mutex :: new ( None ) ) ;
103+ (
104+ Emitter {
105+ slot : Arc :: clone ( & slot) ,
106+ } ,
107+ Receiver { slot } ,
108+ )
109+ }
110+
111+ /// Creates an `TryEmitter`/`Emitter`/`Receiver` triplet
112+ #[ expect(
113+ clippy:: type_complexity,
114+ reason = "three-element tuple is clearer than an alias here"
115+ ) ]
116+ fn try_tx_rx < T , E > ( ) -> (
117+ TryEmitter < T , E > ,
118+ Emitter < Result < T , E > > ,
119+ Receiver < Result < T , E > > ,
120+ ) {
121+ let slot = Arc :: new ( Mutex :: new ( None ) ) ;
122+ (
123+ TryEmitter {
124+ slot : Arc :: clone ( & slot) ,
125+ } ,
126+ Emitter {
127+ slot : Arc :: clone ( & slot) ,
128+ } ,
129+ Receiver { slot } ,
130+ )
131+ }
132+
133+ type SlotRef < T > = Arc < Mutex < Option < T > > > ;
134+
28135/// A handle for emitting values from an [`async_stream`] generator.
29136///
30137/// The generator closure receives an `Emitter<T>` as its argument.
31138pub struct Emitter < T > {
32- slot : Arc < Mutex < Option < T > > > ,
139+ slot : SlotRef < T > ,
33140}
34141
35142/// A handle for emitting values from an [`async_try_stream`] generator.
36143///
37144/// The generator closure receives a `TryEmitter<T, E>` as its argument.
38145pub struct TryEmitter < T , E > {
39- slot : Arc < Mutex < Option < Result < T , E > > > > ,
146+ slot : SlotRef < T > ,
40147}
41148
42149struct Receiver < T > {
43- slot : Arc < Mutex < Option < T > > > ,
150+ slot : SlotRef < T > ,
44151}
45152
46153impl < T > Emitter < T > {
@@ -177,109 +284,6 @@ where
177284 }
178285}
179286
180- fn tx_rx < T > ( ) -> ( Emitter < T > , Receiver < T > ) {
181- let slot = Arc :: new ( Mutex :: new ( None ) ) ;
182- (
183- Emitter {
184- slot : Arc :: clone ( & slot) ,
185- } ,
186- Receiver { slot } ,
187- )
188- }
189-
190- /// Creates a [`Stream`] from an async generator function.
191- ///
192- /// The `generator` closure receives an [`Emitter<T>`] and runs as an async
193- /// block. Each `emitter.emit(value).await` call suspends the generator and
194- /// produces the next item in the stream. The stream ends when the generator
195- /// future resolves.
196- ///
197- /// # Example
198- ///
199- /// ```
200- /// use datafusion_execution::async_stream;
201- /// use futures::StreamExt;
202- ///
203- /// # #[tokio::main(flavor = "current_thread")]
204- /// # async fn main() {
205- /// let stream = async_stream(|mut emitter| async move {
206- /// for i in 0_i32..3 {
207- /// emitter.emit(i).await;
208- /// }
209- /// });
210- ///
211- /// let values: Vec<i32> = stream.collect().await;
212- /// assert_eq!(values, vec![0, 1, 2]);
213- /// # }
214- /// ```
215- pub fn async_stream < T , F : Future < Output = ( ) > > (
216- generator : impl FnOnce ( Emitter < T > ) -> F ,
217- ) -> impl FusedStream < Item = T > {
218- let ( emitter, receiver) = tx_rx ( ) ;
219- AsyncStream :: new ( receiver, generator ( emitter) )
220- }
221-
222- #[ expect(
223- clippy:: type_complexity,
224- reason = "three-element tuple is clearer than an alias here"
225- ) ]
226- fn try_tx_rx < T , E > ( ) -> (
227- TryEmitter < T , E > ,
228- Emitter < Result < T , E > > ,
229- Receiver < Result < T , E > > ,
230- ) {
231- let slot = Arc :: new ( Mutex :: new ( None ) ) ;
232- (
233- TryEmitter {
234- slot : Arc :: clone ( & slot) ,
235- } ,
236- Emitter {
237- slot : Arc :: clone ( & slot) ,
238- } ,
239- Receiver { slot } ,
240- )
241- }
242-
243- /// Creates a fallible [`Stream`] from an async generator function.
244- ///
245- /// The `generator` closure receives a [`TryEmitter<T, E>`] and runs as an
246- /// async block that returns `Result<(), E>`. Each `emitter.emit(value).await`
247- /// call suspends the generator and produces `Ok(value)` as the next stream
248- /// item. The `?` operator can be used inside the generator to short-circuit on
249- /// errors: the error is emitted as the final `Err(e)` item and the stream
250- /// ends. The stream also ends when the generator future resolves to `Ok(())`.
251- ///
252- /// # Example
253- ///
254- /// ```
255- /// use datafusion_execution::async_try_stream;
256- /// use futures::StreamExt;
257- ///
258- /// # #[tokio::main(flavor = "current_thread")]
259- /// # async fn main() {
260- /// let stream = async_try_stream(|mut emitter| async move {
261- /// emitter.emit(1_i32).await;
262- /// emitter.emit(2_i32).await;
263- /// Err::<(), _>("something went wrong")?;
264- /// emitter.emit(3_i32).await; // never reached
265- /// Ok(())
266- /// });
267- ///
268- /// let values: Vec<Result<i32, &str>> = stream.collect().await;
269- /// assert_eq!(values, vec![Ok(1), Ok(2), Err("something went wrong")]);
270- /// # }
271- /// ```
272- pub fn async_try_stream < T , E , F : Future < Output = Result < ( ) , E > > > (
273- generator : impl FnOnce ( TryEmitter < T , E > ) -> F ,
274- ) -> impl FusedStream < Item = Result < T , E > > {
275- let ( try_emitter, mut emitter, receiver) = try_tx_rx :: < T , E > ( ) ;
276- AsyncStream :: new ( receiver, async move {
277- if let Err ( e) = generator ( try_emitter) . await {
278- emitter. emit ( Err ( e) ) . await
279- }
280- } )
281- }
282-
283287#[ cfg( test) ]
284288mod test {
285289 use crate :: async_stream:: Emitter ;
0 commit comments