{"id":17032328,"url":"https://github.com/cbenoit/genoise","last_synced_at":"2025-03-22T21:15:52.751Z","repository":{"id":196335340,"uuid":"695772205","full_name":"CBenoit/genoise","owner":"CBenoit","description":"Minimum viable generators on stable Rust implemented by abusing async/await","archived":false,"fork":false,"pushed_at":"2023-10-13T16:01:41.000Z","size":36,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-15T22:53:59.199Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/CBenoit.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-09-24T07:09:44.000Z","updated_at":"2023-09-25T01:41:29.000Z","dependencies_parsed_at":"2023-10-14T15:21:16.537Z","dependency_job_id":null,"html_url":"https://github.com/CBenoit/genoise","commit_stats":null,"previous_names":["cbenoit/microgen","cbenoit/genoise"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CBenoit%2Fgenoise","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CBenoit%2Fgenoise/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CBenoit%2Fgenoise/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CBenoit%2Fgenoise/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CBenoit","download_url":"https://codeload.github.com/CBenoit/genoise/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245020341,"owners_count":20548180,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-14T08:28:02.785Z","updated_at":"2025-03-22T21:15:52.590Z","avatar_url":"https://github.com/CBenoit.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"genoise\n=======\n\n[\u003cimg alt=\"github\" src=\"https://img.shields.io/badge/github-cbenoit/genoise-8da0cb?style=for-the-badge\u0026labelColor=555555\u0026logo=github\" height=\"20\"\u003e](https://github.com/CBenoit/genoise)\n[\u003cimg alt=\"crates.io\" src=\"https://img.shields.io/crates/v/genoise.svg?style=for-the-badge\u0026color=fc8d62\u0026logo=rust\" height=\"20\"\u003e](https://crates.io/crates/genoise)\n[\u003cimg alt=\"docs.rs\" src=\"https://img.shields.io/badge/docs.rs-genoise-66c2a5?style=for-the-badge\u0026labelColor=555555\u0026logo=docs.rs\" height=\"20\"\u003e](https://docs.rs/genoise)\n[\u003cimg alt=\"build status\" src=\"https://img.shields.io/github/actions/workflow/status/CBenoit/genoise/ci.yml?branch=main\u0026style=for-the-badge\" height=\"20\"\u003e](https://github.com/CBenoit/genoise/actions?query=branch%3Amain)\n\n*Compiler support: requires rustc 1.65+*\n\n## What is `genoise`?\n\n`genoise` implements [generators][generator] (a weaker, special case of coroutines) for stable Rust. \nInstead of using `#![feature(generators, generator_trait)]` and the `yield` keyword, [\"extra-unstable\"\nfeatures][unstable-generators] in the Rust compiler, `async`/`await` syntax is used.\n\nCommon use cases are:\n\n- Defining iterators with self-referential states without writing `unsafe` code.\n- Building state machines like it’s imperative code and leaving the compiler do the rest.\n\n[generator]: https://en.wikipedia.org/wiki/Generator_(computer_programming)\n[unstable-generators]: https://doc.rust-lang.org/nightly/unstable-book/language-features/generators.html\n\n## What is a generator?\n\nA generator control the flow of three types of data:\n\n- Yield type: Each time a generator suspends execution, a value is handed to the caller.\n- Resume type: Each time a generator is resumed, a value is passed in by the caller.\n- Output type: When a generator completes, one final value is returned.\n\nHere is an example taking advantage of this:\n\n```rust\nuse genoise::local::{Gn, Co};\nuse genoise::GnState;\n\nasync fn my_generator\u003c'a\u003e(mut co: Co\u003c'_, usize, bool\u003e, input: \u0026'a str) -\u003e \u0026'a str {\n    let mut trimmed = input;\n\n    while co.suspend(trimmed.len()).await {\n        trimmed = \u0026trimmed[..trimmed.len() - 1];\n    }\n\n    trimmed\n}\n\nlet argument = \"1234567890\";\n\nlet mut generator = Gn::new(|co| my_generator(co, argument));\n\n// A generator does nothing when created, you need to `.start()` it first\nassert!(!generator.started());\nassert!(matches!(generator.start(), GnState::Suspended(10)));\n\n// Once started, you can pass in data and resume the execution using `.resume(…)`\nassert!(generator.started());\nassert!(matches!(generator.resume(true), GnState::Suspended(9)));\nassert!(matches!(generator.resume(true), GnState::Suspended(8)));\nassert!(matches!(generator.resume(false), GnState::Completed(\"12345678\")));\n```\n\n## Why `genoise`?\n\n- Low maintenance: `genoise` is a zero-dependency crate. There is no need to release a new version\n  of `genoise` solely for transitive dependencies.\n- Lightweight: `genoise` consists of only a few hundred lines of code and does not rely on\n  procedural macros.\n- Doesn’t attempt to use reserved keywords: there are no `yield_` or `r#yield` in its API.\n- Concise and simple: there is more example and test code than actual library code.\n  You can read and grok its source code in just a few minutes.\n  The most challenging part is [`GeneratorFlavor`](crate::GeneratorFlavor), which relies on GATs (Generic Associated Types).\n- Supports continuation arguments and completion values.\n- Provides allocation-free generators at user’s option.\n- Genericity over the [`GeneratorFlavor`](crate::GeneratorFlavor): Write once, use everywhere.\n- No standard library: `genoise` is a no-std crate, and the `alloc` feature can be disabled.\n- Not a concurrency framework or async runtime: `genoise` does not aim to replace `tokio` or\n  `smol`, and it does not contain platform-specific code.\n\n## Why not `genoise`?\n\n- You prefer an API closer to the actual generators available on Rust nightly.\n- You are writing performance-sensitive code, and need to use the generator in a tight loop.\n  `genoise` has not been bencharked and will probably slow down your program.\n  Outside of a tight loop the cost is likely negligible.\n\n## Flavor comparison\n\n|                             | [`local::StackGn`] | [`local::Gn`] | [`sync::StackGn`] | [`sync::Gn`] |\n|-----------------------------|--------------------|---------------|-------------------|--------------|\n| Allocations per instance    | 0                  | 2             | 0                 | 2            |\n| Can be returned             | No                 | Yes           | No                | Yes          |\n| Thread-safe (`Sync + Send`) | No                 | No            | Yes               | yes          |\n\n\"local\" here is used like in thread-\"local\".\n\nConstructing a heap-flavored generator requires two allocations:\n\n- A memory slot to share the yield and resume values\n- A memory slot for the `Future`-based state machine\n\nStack-flavored generators are relying on \"[local pinning][local-pinning]\" for the underlying\n`Future`, and the memory slots for the yield and resume values are standard `\u0026T` references\npointing elsewhere, most likely to a local memory region.\nAs such, in most cases these generators can’t be returned from functions.\nHowever, it’s generally not a problem to transfer the ownership as long as the new owner does\nnot outlive the memory slots.\n\n[local-pinning]: https://doc.rust-lang.org/std/pin/macro.pin.html\n\n## Unsafe usages\n\nTODO: expand on this\n\nSafety blocks are properly documented.\n\n- noop RawWaker\n- SyncRefCell (~= kind of spinlock but without spinning)\n\n## Allocation-free example\n\nTODO: elaborate this section\n\n```rust\nuse genoise::local::{StackGn, StackCo, let_gen};\nuse genoise::GnState;\n\nasync fn my_generator\u003c'a\u003e(mut co: StackCo\u003c'_, usize, bool\u003e, input: \u0026'a str) -\u003e \u0026'a str {\n    let mut trimmed = input;\n\n    while co.suspend(trimmed.len()).await {\n        trimmed = \u0026trimmed[..trimmed.len() - 1];\n    }\n\n    trimmed\n}\n\nlet argument = \"1234567890\";\n\nlet_gen!(generator, |co| { my_generator(co, argument) }); // \u003c- let_gen! helper macro\n\nassert!(matches!(generator.start(), GnState::Suspended(10)));\nassert!(matches!(generator.resume(true), GnState::Suspended(9)));\nassert!(matches!(generator.resume(false), GnState::Completed(\"123456789\")));\n```\n\n## Flavor-agnostic example\n\nTODO: elaborate this section\n\n```rust\nuse genoise::{local, sync};\nuse genoise::{GnState, Co, GeneratorFlavor};\n\nasync fn my_generator\u003c'a, F\u003e(mut co: Co\u003c'_, usize, bool, F\u003e, input: \u0026'a str) -\u003e \u0026'a str\nwhere\n    F: GeneratorFlavor,\n{\n    let mut trimmed = input;\n\n    while co.suspend(trimmed.len()).await {\n        trimmed = \u0026trimmed[..trimmed.len() - 1];\n    }\n\n    trimmed\n}\n\nlet argument = \"1234567890\";\n\n{\n    // Local stack-flavored\n    local::let_gen!(generator, |co| { my_generator(co, argument) });\n    assert!(matches!(generator.start(), GnState::Suspended(10)));\n    assert!(matches!(generator.resume(false), GnState::Completed(\"1234567890\")));\n}\n\n{\n    // Local heap-flavored\n    let mut generator = local::Gn::new(|co| my_generator(co, argument));\n    assert!(matches!(generator.start(), GnState::Suspended(10)));\n    assert!(matches!(generator.resume(false), GnState::Completed(\"1234567890\")));\n}\n\n{\n    // Thread-safe stack-flavored\n    sync::let_gen!(generator, |co| { my_generator(co, argument) });\n\n    std::thread::scope(|s| {\n        s.spawn(|| {\n            assert!(matches!(generator.start(), GnState::Suspended(10)));\n            assert!(matches!(generator.resume(false), GnState::Completed(\"1234567890\")));\n        });\n    });\n}\n\n{\n    // Thread-safe heap-flavored\n    let mut generator = sync::Gn::new(|co| my_generator(co, argument));\n\n    let handle = std::thread::spawn(move || {\n        assert!(matches!(generator.start(), GnState::Suspended(10)));\n        assert!(matches!(generator.resume(false), GnState::Completed(\"1234567890\")));\n    });\n\n    handle.join().unwrap();\n}\n```\n\n## Relation with `Iterator`s\n\nA generator which does not take any value when resumed nor returns any value on completion is\nalso an [`Iterator`](core::iter::Iterator):\n\n```rust\nuse genoise::local::{Gn, Co};\n\nasync fn fibonacci(mut co: Co\u003c'_, u32, ()\u003e) {\n    let mut a = 0;\n    co.suspend(a).await;\n\n    let mut b = 1;\n    co.suspend(b).await;\n\n    while b \u003c 200 {\n        core::mem::swap(\u0026mut a, \u0026mut b);\n        b += a;\n\n        co.suspend(b).await;\n    }\n}\n\nlet generator = Gn::new(fibonacci);\nlet fibonacci_sequence: Vec\u003cu32\u003e = generator.collect();\nassert_eq!(\n    fibonacci_sequence,\n    [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233]\n);\n```\n\nNote that calling [`size_hint`](core::iter::Iterator::size_hint) on a generator will always return\n`(0, None)` since there is no way to know how many items will be yielded by the generator.\nSome generators may never terminate at all (it is advised to not call\n[`collect`](core::iter::Iterator::collect) on these).\n\n## Alternatives to `genoise`\n\n- [genawaiter](https://crates.io/crates/genawaiter): Stackless generators on stable Rust\n- [next_gen](https://crates.io/crates/next_gen): Safe generators on stable Rust\n- [generator](https://crates.io/crates/generator): Stackfull Generator Library in Rust\n- [remit](https://crates.io/crates/remit): Rust generators implemented through async/await syntax\n- [gen-z](https://crates.io/crates/gen-z): Macro-free stream construction through asynchronous generators via an awaitable sender\n- [corosensei](https://crates.io/crates/corosensei): A fast and safe implementation of stackful coroutines\n- [may](https://crates.io/crates/may): Rust Stackful Coroutine Library\n- [mco](https://crates.io/crates/mco): Rust Coroutine Library like go\n\n## License\n\n\u003csup\u003e\nLicensed under either of \u003ca href=\"LICENSE-APACHE\"\u003eApache License, Version\n2.0\u003c/a\u003e, \u003ca href=\"LICENSE-MIT\"\u003eMIT license\u003c/a\u003e or \u003ca href=\"LICENSE-ZLIB\"\u003eZlib license\u003c/a\u003e\nat your option.\n\u003c/sup\u003e\n\n\u003cbr\u003e\n\n\u003csub\u003e\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in this crate by you shall be licensed as above, without any\nadditional terms or conditions.\n\u003c/sub\u003e","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcbenoit%2Fgenoise","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcbenoit%2Fgenoise","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcbenoit%2Fgenoise/lists"}