├── .gitignore ├── Cargo.toml ├── README.md ├── propane-macros ├── Cargo.toml └── src │ ├── elision.rs │ └── lib.rs ├── src └── lib.rs └── tests ├── generators.rs └── stream.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "propane" 3 | version = "0.1.0" 4 | authors = ["Without Boats "] 5 | edition = "2018" 6 | license = "MIT OR Apache-2.0" 7 | 8 | description = "a generator syntax experiment" 9 | repository = "https://github.com/withoutboats/propane" 10 | documentation = "https://docs.rs/propane" 11 | keywords = ["iterators", "generators"] 12 | 13 | [dependencies] 14 | futures-core = "0.3.5" 15 | 16 | [dependencies.propane-macros] 17 | path = "./propane-macros" 18 | version = "0.1.0" 19 | 20 | [workspace] 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # propane - Rust generators 2 | 3 | Propane is a thin wrapper around the unstable generator feature, allowing users to create free 4 | functions as generators. It implements a particular idea for how generators should be designed; a 5 | big part of why generators are still unstable today is that these design questions are still 6 | undecided. 7 | 8 | The syntax looks like this: 9 | 10 | ```rust 11 | #[propane::generator] 12 | fn foo() -> i32 { 13 | for n in 0i32..10 { 14 | yield n; 15 | } 16 | } 17 | ``` 18 | 19 | Because it is a macro, it does not work as well as a native language feature would, and has worse 20 | error messages. 21 | 22 | ## Design decisions 23 | 24 | Propane is designed to allow users to write generators for the purpose of implementing iterators. 25 | For that reason, its generators are restricted in some important ways. These are the intentional 26 | design restrictions of propane (that is, these are not limitations because of bugs, they are not 27 | intended to be lifted): 28 | 29 | 1. A propane generator becomes a function that returns an `impl Iterator`; the iterator interface is 30 | the only interface users can use with the generator's return type. 31 | 2. A propane generator can only return `()`, it cannot yield one type and then return another 32 | interesting type. The `?` operator yields the error and then, on the next resumption, returns. 33 | 3. A propane generator implements Unpin, and cannot be self-referential (unlike async functions). 34 | 35 | ## Notes on the Unpin requirement 36 | 37 | Because of the signature of `Iterator::next`, it is always safe to move iterators between calls to 38 | `next`. This makes unboxed, self-referential iterators unsound. We did not have `Pin` when we 39 | designed the Iterator API. 40 | 41 | However, in general, users can push unowned data outside of the iterator in a way they can't with 42 | futures. Futures, usually, ultimately have to be `'static`, so they can spawned, but iterators 43 | usually are consumed in a way that does not require them to own all of their data. 44 | 45 | Therefore, it is potentially the case that generators restricted to not contain self-references are 46 | sufficient for this use case. Propane intends to explore that possibility. 47 | -------------------------------------------------------------------------------- /propane-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "propane-macros" 3 | version = "0.1.0" 4 | authors = ["Without Boats "] 5 | edition = "2018" 6 | license = "MIT OR Apache-2.0" 7 | repository = "https://github.com/withoutboats/propane" 8 | description = "proc macros for the propane crate" 9 | 10 | [dependencies] 11 | quote = "1.0.7" 12 | proc-macro2 = "1.0.19" 13 | 14 | [dependencies.syn] 15 | version = "1.0.35" 16 | features = ["fold", "full", "parsing"] 17 | 18 | [lib] 19 | proc-macro = true 20 | -------------------------------------------------------------------------------- /propane-macros/src/elision.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Span}; 2 | use syn::*; 3 | use syn::punctuated::Punctuated; 4 | use syn::token::Comma; 5 | use syn::fold::Fold; 6 | 7 | pub fn unelide_lifetimes(generics: &mut Punctuated, args: Punctuated) 8 | -> Punctuated 9 | { 10 | let mut folder = UnelideLifetimes::new(generics); 11 | args.into_iter().map(|arg| folder.fold_fn_arg(arg)).collect() 12 | } 13 | 14 | struct UnelideLifetimes<'a> { 15 | generics: &'a mut Punctuated, 16 | lifetime_index: usize, 17 | lifetime_name: String, 18 | count: u32, 19 | } 20 | 21 | impl<'a> UnelideLifetimes<'a> { 22 | fn new(generics: &'a mut Punctuated) -> UnelideLifetimes<'a> { 23 | let lifetime_index = lifetime_index(generics); 24 | let lifetime_name = lifetime_name(generics); 25 | UnelideLifetimes { 26 | generics, 27 | lifetime_index, 28 | lifetime_name, 29 | count: 0 30 | } 31 | 32 | } 33 | 34 | // Constitute a new lifetime 35 | fn new_lifetime(&mut self) -> Lifetime { 36 | let lifetime_name = format!("{}{}", self.lifetime_name, self.count); 37 | let lifetime = Lifetime::new(&lifetime_name, Span::call_site()); 38 | 39 | let idx = self.lifetime_index + self.count as usize; 40 | self.generics.insert(idx, GenericParam::Lifetime(LifetimeDef::new(lifetime.clone()))); 41 | self.count += 1; 42 | 43 | lifetime 44 | } 45 | 46 | // Take an Option and guarantee its an unelided lifetime 47 | fn expand_lifetime(&mut self, lifetime: Option) -> Lifetime { 48 | match lifetime { 49 | Some(l) => self.fold_lifetime(l), 50 | None => self.new_lifetime(), 51 | } 52 | } 53 | } 54 | 55 | impl<'a> Fold for UnelideLifetimes<'a> { 56 | // If the lifetime is `'_`, replace it with a new unelided lifetime 57 | fn fold_lifetime(&mut self, lifetime: Lifetime) -> Lifetime { 58 | if lifetime.to_string() == "'_" { self.new_lifetime() } 59 | else { lifetime } 60 | } 61 | 62 | // If the reference's lifetime is elided, replace it with a new unelided lifetime 63 | fn fold_type_reference(&mut self, ty_ref: TypeReference) -> TypeReference { 64 | let TypeReference { and_token, lifetime, mutability, elem } = ty_ref; 65 | let lifetime = Some(self.expand_lifetime(lifetime)); 66 | let elem = Box::new(self.fold_type(*elem)); 67 | TypeReference { and_token, lifetime, mutability, elem } 68 | } 69 | 70 | fn fold_receiver(&mut self, receiver: Receiver) -> Receiver { 71 | let reference = receiver.reference.map(|(and, lifetime)| { 72 | let lifetime = self.expand_lifetime(lifetime); 73 | (and, Some(lifetime)) 74 | }); 75 | 76 | Receiver { reference, ..receiver } 77 | } 78 | } 79 | 80 | fn lifetime_index(generics: &Punctuated) -> usize { 81 | generics.iter() 82 | .take_while(|param| if let GenericParam::Lifetime(_) = param { true } else { false }) 83 | .count() 84 | } 85 | 86 | // Determine the prefix for all lifetime names. Ensure it doesn't 87 | // overlap with any existing lifetime names. 88 | fn lifetime_name(generics: &Punctuated) -> String { 89 | let mut lifetime_name = String::from("'_async"); 90 | let existing_lifetimes: Vec = generics.iter().filter_map(|param| { 91 | if let GenericParam::Lifetime(LifetimeDef { lifetime, .. }) = param { Some(lifetime.to_string()) } 92 | else { None } 93 | }).collect(); 94 | while existing_lifetimes.iter().any(|name| name.starts_with(&lifetime_name)) { 95 | lifetime_name.push('_'); 96 | } 97 | lifetime_name 98 | } 99 | -------------------------------------------------------------------------------- /propane-macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | 3 | mod elision; 4 | 5 | use proc_macro::*; 6 | use syn::fold::Fold; 7 | use syn::{ReturnType, Token}; 8 | 9 | use proc_macro2::Span; 10 | 11 | #[proc_macro_attribute] 12 | pub fn generator(_: TokenStream, input: TokenStream) -> TokenStream { 13 | let mut folder = Generator { 14 | outer_fn: true, 15 | is_async: false, 16 | is_move: true, 17 | lifetimes: vec![] 18 | }; 19 | if let Ok(item_fn) = syn::parse(input.clone()) { 20 | let item_fn = folder.fold_item_fn(item_fn); 21 | quote::quote!(#item_fn).into() 22 | } else if let Ok(method) = syn::parse(input) { 23 | let method = folder.fold_impl_item_method(method); 24 | quote::quote!(#method).into() 25 | } else { 26 | panic!("#[generator] atribute can only be applied to functions"); 27 | } 28 | } 29 | 30 | #[proc_macro] 31 | pub fn gen(input: TokenStream) -> TokenStream { 32 | let mut folder = Generator { 33 | outer_fn: false, 34 | is_async: false, 35 | is_move: false, 36 | lifetimes: vec![] 37 | }; 38 | let block = folder.block(input.into()); 39 | quote::quote!(#block).into() 40 | } 41 | 42 | #[proc_macro] 43 | pub fn gen_move(input: TokenStream) -> TokenStream { 44 | let mut folder = Generator { 45 | outer_fn: false, 46 | is_async: false, 47 | is_move: true, 48 | lifetimes: vec![] 49 | }; 50 | let block = folder.block(input.into()); 51 | quote::quote!(#block).into() 52 | } 53 | 54 | #[proc_macro] 55 | pub fn async_gen(input: TokenStream) -> TokenStream { 56 | let mut folder = Generator { 57 | outer_fn: false, 58 | is_async: true, 59 | is_move: false, 60 | lifetimes: vec![] 61 | }; 62 | let block = folder.block(input.into()); 63 | quote::quote!(#block).into() 64 | } 65 | 66 | #[proc_macro] 67 | pub fn async_gen_move(input: TokenStream) -> TokenStream { 68 | let mut folder = Generator { 69 | outer_fn: false, 70 | is_async: true, 71 | is_move: true, 72 | lifetimes: vec![] 73 | }; 74 | let block = folder.block(input.into()); 75 | quote::quote!(#block).into() 76 | } 77 | 78 | struct Generator { 79 | outer_fn: bool, 80 | is_async: bool, 81 | is_move: bool, 82 | lifetimes: Vec, 83 | } 84 | 85 | impl Generator { 86 | fn block(&mut self, input: proc_macro2::TokenStream) -> syn::Block { 87 | let block = syn::parse2(quote::quote!({ #input })).unwrap(); 88 | let block = self.fold_block(block); 89 | self.finish(&block) 90 | } 91 | 92 | fn finish(&self, block: &syn::Block) -> syn::Block { 93 | let move_token = match self.is_move { 94 | true => Some(Token![move](Span::call_site())), 95 | false => None, 96 | }; 97 | if !self.is_async { 98 | syn::parse2(quote::quote! {{ 99 | let __ret = #move_token || { 100 | #block; 101 | #[allow(unreachable_code)] 102 | { 103 | return; 104 | yield panic!(); 105 | } 106 | }; 107 | 108 | #[allow(unreachable_code)] 109 | propane::__internal::GenIter(__ret) 110 | }}).unwrap() 111 | } else { 112 | syn::parse2(quote::quote! {{ 113 | let __ret = static #move_token |mut __propane_stream_ctx| { 114 | #block; 115 | #[allow(unreachable_code)] 116 | { 117 | return; 118 | yield panic!(); 119 | } 120 | }; 121 | 122 | #[allow(unreachable_code)] 123 | unsafe { propane::__internal::GenStream::new(__ret) } 124 | }}).unwrap() 125 | } 126 | } 127 | } 128 | 129 | impl Fold for Generator { 130 | fn fold_item_fn(&mut self, mut i: syn::ItemFn) -> syn::ItemFn { 131 | if !self.outer_fn { return i } 132 | 133 | let inputs = elision::unelide_lifetimes(&mut i.sig.generics.params, i.sig.inputs); 134 | self.lifetimes = i.sig.generics.lifetimes().map(|l| l.lifetime.clone()).collect(); 135 | 136 | self.is_async = i.sig.asyncness.is_some(); 137 | 138 | let output = self.fold_return_type(i.sig.output); 139 | let sig = syn::Signature { output, inputs, asyncness: None, ..i.sig }; 140 | 141 | self.outer_fn = false; 142 | 143 | let inner = self.fold_block(*i.block); 144 | let block = Box::new(self.finish(&inner)); 145 | 146 | syn::ItemFn { sig, block, ..i } 147 | } 148 | 149 | fn fold_impl_item_method(&mut self, mut i: syn::ImplItemMethod) -> syn::ImplItemMethod { 150 | if !self.outer_fn { return i } 151 | 152 | let inputs = elision::unelide_lifetimes(&mut i.sig.generics.params, i.sig.inputs); 153 | self.lifetimes = i.sig.generics.lifetimes().map(|l| l.lifetime.clone()).collect(); 154 | 155 | self.is_async = i.sig.asyncness.is_some(); 156 | 157 | let output = self.fold_return_type(i.sig.output); 158 | let sig = syn::Signature { output, inputs, asyncness: None, ..i.sig }; 159 | 160 | let inner = self.fold_block(i.block); 161 | let block = self.finish(&inner); 162 | 163 | syn::ImplItemMethod { sig, block, ..i } 164 | } 165 | 166 | fn fold_return_type(&mut self, i: syn::ReturnType) -> syn::ReturnType { 167 | if !self.outer_fn { return i; } 168 | 169 | let (arrow, ret) = match i { 170 | ReturnType::Default => (Token![->](Span::call_site()), syn::parse_str("()").unwrap()), 171 | ReturnType::Type(arrow, ty) => (arrow, *ty), 172 | }; 173 | let lifetimes = std::mem::replace(&mut self.lifetimes, vec![]); 174 | 175 | let ret = if self.is_async { 176 | syn::parse2(quote::quote!((impl propane::__internal::Stream #(+ #lifetimes )*))).unwrap() 177 | } else { 178 | syn::parse2(quote::quote!((impl Iterator #(+ #lifetimes )*))).unwrap() 179 | }; 180 | ReturnType::Type(arrow, Box::new(ret)) 181 | } 182 | 183 | fn fold_expr(&mut self, i: syn::Expr) -> syn::Expr { 184 | match i { 185 | // Stream modifiers 186 | syn::Expr::Try(syn::ExprTry { expr, .. }) if self.is_async => { 187 | let expr = self.fold_expr(*expr); 188 | syn::parse2(quote::quote!(propane::async_gen_try!(#expr))).unwrap() 189 | } 190 | syn::Expr::Yield(syn::ExprYield { expr: Some(expr), .. }) if self.is_async => { 191 | let expr = self.fold_expr(*expr); 192 | syn::parse2(quote::quote!(propane::async_gen_yield!(#expr))).unwrap() 193 | } 194 | syn::Expr::Yield(syn::ExprYield { expr: None, .. }) if self.is_async => { 195 | syn::parse2(quote::quote!(propane::async_gen_yield!(()))).unwrap() 196 | } 197 | syn::Expr::Await(syn::ExprAwait { base: expr, ..}) if self.is_async => { 198 | let expr = self.fold_expr(*expr); 199 | syn::parse2(quote::quote!(propane::async_gen_await!(#expr, __propane_stream_ctx))).unwrap() 200 | } 201 | 202 | // Iterator modifiers 203 | syn::Expr::Try(syn::ExprTry { expr, .. }) => { 204 | let expr = self.fold_expr(*expr); 205 | syn::parse2(quote::quote!(propane::gen_try!(#expr))).unwrap() 206 | } 207 | 208 | // Everything else 209 | _ => syn::fold::fold_expr(self, i) 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This library provides a generator macro, to define generators. 2 | //! 3 | //! It is intended to explore one particular point in the design space of generators. More 4 | //! documentation can be found in the description of the attribute. 5 | #![feature(generator_trait)] 6 | #![no_std] 7 | 8 | /// This macro can be applied to functions to make them into generators. 9 | /// 10 | /// Functions annotated with this attribute use the `yield` keyword, instead of the `return` 11 | /// keyword. They yield an item and then continue. When you call a generator function, you get an 12 | /// iterator of the type it yields, rather than just one value of that type. 13 | /// 14 | /// You can still use the `return` keyword to terminate the generator early, but the `return` 15 | /// keyword cannot take a value; it only terminates the function. 16 | /// 17 | /// The behavior of `?` is also modified in these functions. In the event of an error, the 18 | /// generator yields the error value, and then the next time it is resumed it returns `None`. 19 | /// 20 | /// ## Forbidding self-references 21 | /// 22 | /// Unlike async functions, generators cannot contain self-references: a reference into their stack 23 | /// space that exists across a yield point. Instead, anything you wish to have by reference you 24 | /// should move out of the state of the generator, taking it as an argument, or else not holding it 25 | /// by reference across a point that you yield. 26 | /// 27 | /// ## Unstable features 28 | /// 29 | /// In order to use this attribute, you must turn on all of these features: 30 | /// - `generators` 31 | /// - `generator_trait` 32 | /// - `try_trait` 33 | /// 34 | /// ## Example 35 | /// 36 | /// ```rust 37 | /// #![feature(generators, generator_trait, try_trait)] 38 | /// 39 | /// #[propane::generator] 40 | /// fn fizz_buzz() -> String { 41 | /// for x in 1..101 { 42 | /// match (x % 3 == 0, x % 5 == 0) { 43 | /// (true, true) => yield String::from("FizzBuzz"), 44 | /// (true, false) => yield String::from("Fizz"), 45 | /// (false, true) => yield String::from("Buzz"), 46 | /// (..) => yield x.to_string(), 47 | /// } 48 | /// } 49 | /// } 50 | /// 51 | /// fn main() { 52 | /// let mut fizz_buzz = fizz_buzz(); 53 | /// assert_eq!(&fizz_buzz.next().unwrap()[..], "1"); 54 | /// assert_eq!(&fizz_buzz.next().unwrap()[..], "2"); 55 | /// assert_eq!(&fizz_buzz.next().unwrap()[..], "Fizz"); 56 | /// assert_eq!(&fizz_buzz.next().unwrap()[..], "4"); 57 | /// assert_eq!(&fizz_buzz.next().unwrap()[..], "Buzz"); 58 | /// assert_eq!(&fizz_buzz.next().unwrap()[..], "Fizz"); 59 | /// assert_eq!(&fizz_buzz.next().unwrap()[..], "7"); 60 | /// 61 | /// // yada yada yada 62 | /// let mut fizz_buzz = fizz_buzz.skip(90); 63 | /// 64 | /// assert_eq!(&fizz_buzz.next().unwrap()[..], "98"); 65 | /// assert_eq!(&fizz_buzz.next().unwrap()[..], "Fizz"); 66 | /// assert_eq!(&fizz_buzz.next().unwrap()[..], "Buzz"); 67 | /// assert!(fizz_buzz.next().is_none()); 68 | /// } 69 | /// ``` 70 | pub use propane_macros::generator; 71 | pub use propane_macros::gen; 72 | pub use propane_macros::gen_move; 73 | pub use propane_macros::async_gen; 74 | pub use propane_macros::async_gen_move; 75 | 76 | #[doc(hidden)] 77 | pub mod __internal { 78 | use core::marker::Unpin; 79 | use core::ops::{Generator, GeneratorState}; 80 | use core::pin::Pin; 81 | use core::task::{Context, Poll}; 82 | 83 | pub use futures_core::Stream; 84 | 85 | pub struct GenIter(pub G); 86 | 87 | impl + Unpin> Iterator for GenIter { 88 | type Item = G::Yield; 89 | 90 | fn next(&mut self) -> Option { 91 | match Pin::new(&mut self.0).resume(()) { 92 | GeneratorState::Yielded(item) => Some(item), 93 | GeneratorState::Complete(()) => None, 94 | } 95 | } 96 | } 97 | 98 | pub struct GenStream(G); 99 | 100 | impl GenStream { 101 | pub unsafe fn new(gen: G) -> GenStream { GenStream(gen) } 102 | } 103 | 104 | impl, Return = ()>, T> Stream for GenStream { 105 | type Item = T; 106 | 107 | fn poll_next(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { 108 | let ctx: *mut () = ctx as *mut Context<'_> as *mut (); 109 | 110 | unsafe { 111 | let gen: Pin<&mut G> = Pin::map_unchecked_mut(self, |this| &mut this.0); 112 | 113 | match gen.resume(ctx) { 114 | GeneratorState::Complete(()) => Poll::Ready(None), 115 | GeneratorState::Yielded(Poll::Ready(item)) => Poll::Ready(Some(item)), 116 | GeneratorState::Yielded(Poll::Pending) => Poll::Pending, 117 | } 118 | } 119 | } 120 | } 121 | 122 | #[doc(hidden)] 123 | #[macro_export] 124 | macro_rules! gen_try { 125 | ($e:expr) => {{ 126 | use core::ops::Try; 127 | match Try::into_result($e) { 128 | Ok(ok) => ok, 129 | Err(err) => { 130 | yield <_ as Try>::from_error(err); 131 | return; 132 | } 133 | } 134 | }} 135 | } 136 | 137 | #[doc(hidden)] 138 | #[macro_export] 139 | macro_rules! async_gen_try { 140 | ($e:expr) => {{ 141 | use core::ops::Try; 142 | match Try::into_result($e) { 143 | Ok(ok) => ok, 144 | Err(err) => { 145 | yield core::task::Poll::Ready(<_ as Try>::from_error(err)); 146 | return; 147 | } 148 | } 149 | }} 150 | } 151 | 152 | #[doc(hidden)] 153 | #[macro_export] 154 | macro_rules! async_gen_yield { 155 | ($e:expr) => {{ 156 | yield core::task::Poll::Ready($e) 157 | }} 158 | } 159 | 160 | #[doc(hidden)] 161 | #[macro_export] 162 | macro_rules! async_gen_await { 163 | ($e:expr, $ctx:expr) => {{ 164 | unsafe { 165 | use core::pin::Pin; 166 | use core::task::{Poll, Context}; 167 | let ctx = &mut *($ctx as *mut Context<'_>); 168 | let mut e = $e; 169 | let mut future = Pin::new_unchecked(&mut e); 170 | loop { 171 | match core::future::Future::poll(Pin::as_mut(&mut future), ctx) { 172 | Poll::Ready(x) => break x, 173 | Poll::Pending => $ctx = yield Poll::Pending, 174 | } 175 | } 176 | } 177 | }} 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /tests/generators.rs: -------------------------------------------------------------------------------- 1 | #![feature(generators, generator_trait, try_trait)] 2 | #![no_std] 3 | 4 | #[propane::generator] 5 | fn foo() -> i32 { 6 | for n in 0..10 { 7 | yield n; 8 | } 9 | } 10 | 11 | #[test] 12 | fn test_foo() { 13 | let mut foo = foo(); 14 | for n in 0..10 { 15 | assert_eq!(foo.next(), Some(n)); 16 | } 17 | assert!(foo.next().is_none()); 18 | } 19 | 20 | #[propane::generator] 21 | fn result() -> Result { 22 | fn bar() -> Result<(), ()> { 23 | Err(()) 24 | } 25 | 26 | for n in 0..5 { 27 | yield Ok(n); 28 | } 29 | 30 | bar()?; 31 | 32 | yield Ok(10); // will not be evaluated 33 | } 34 | 35 | #[test] 36 | fn test_result() { 37 | let mut result = result(); 38 | for n in 0..5 { 39 | assert_eq!(result.next(), Some(Ok(n))); 40 | } 41 | 42 | assert_eq!(result.next(), Some(Err(()))); 43 | assert!(result.next().is_none()) 44 | } 45 | 46 | struct Foo(Option); 47 | 48 | impl Foo { 49 | #[propane::generator] 50 | fn method(&mut self) -> i32 { 51 | while let Some(n) = self.0.take() { 52 | yield n; 53 | } 54 | } 55 | } 56 | 57 | #[test] 58 | fn test_foo_method() { 59 | let mut foo = Foo(Some(0)); 60 | let mut iter = foo.method(); 61 | assert_eq!(iter.next(), Some(0)); 62 | assert!(iter.next().is_none()); 63 | } 64 | 65 | #[test] 66 | fn anonymous_generator() { 67 | let mut iter = propane::gen! { 68 | for x in 0..10 { 69 | yield x; 70 | } 71 | }; 72 | 73 | for x in 0..10 { 74 | assert_eq!(iter.next(), Some(x)); 75 | } 76 | 77 | assert!(iter.next().is_none()); 78 | } 79 | -------------------------------------------------------------------------------- /tests/stream.rs: -------------------------------------------------------------------------------- 1 | #![feature(generators, generator_trait, try_trait)] 2 | 3 | use std::future::Future; 4 | 5 | #[propane::generator] 6 | async fn foo(fut: F) -> i32 { 7 | fut.await; 8 | yield 0i32; 9 | } 10 | 11 | #[propane::generator] 12 | async fn stream>(futures: Vec) -> T { 13 | for future in futures { 14 | yield future.await; 15 | } 16 | } 17 | --------------------------------------------------------------------------------