{"id":17874271,"url":"https://github.com/modprog/forr","last_synced_at":"2026-02-20T01:02:09.018Z","repository":{"id":165925344,"uuid":"635312030","full_name":"ModProg/forr","owner":"ModProg","description":"meta-for","archived":false,"fork":false,"pushed_at":"2024-07-25T22:54:15.000Z","size":83,"stargazers_count":1,"open_issues_count":6,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-17T09:34:31.552Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/ModProg.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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-05-02T12:31:27.000Z","updated_at":"2024-07-25T22:54:18.000Z","dependencies_parsed_at":"2024-10-28T11:37:48.290Z","dependency_job_id":"d165f961-3ec7-480f-867d-a93d4fa2acee","html_url":"https://github.com/ModProg/forr","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/ModProg/forr","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ModProg%2Fforr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ModProg%2Fforr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ModProg%2Fforr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ModProg%2Fforr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ModProg","download_url":"https://codeload.github.com/ModProg/forr/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ModProg%2Fforr/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29637917,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-19T22:32:43.237Z","status":"ssl_error","status_checked_at":"2026-02-19T22:32:38.330Z","response_time":117,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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-28T11:08:17.360Z","updated_at":"2026-02-20T01:02:08.989Z","avatar_url":"https://github.com/ModProg.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"### forr\n\n[![CI Status](https://github.com/ModProg/forr/actions/workflows/test.yaml/badge.svg)](https://github.com/ModProg/forr/actions/workflows/test.yaml)\n[![Crates.io](https://img.shields.io/crates/v/forr)](https://crates.io/crates/forr)\n[![Docs.rs](https://img.shields.io/crates/v/template?color=informational\u0026label=docs.rs)](https://docs.rs/forr)\n[![Documentation for `main`](https://img.shields.io/badge/docs-main-informational)](https://modprog.github.io/forr/forr/)\n\n[Control flow](#iff) and [loops](#forr) in compile time.\n\n### `forr!`\n\nAims to replace single use [`macro_rules!`](https://doc.rust-lang.org/reference/macros-by-example.html) for the purpose to repeating code.\n\nFor example it can reduce an implementation for multiple tuples:\n```rust\nuse forr::forr;\ntrait Like { fn like (\u0026self, other: \u0026Self) -\u003e bool; }\nimpl Like for i32 { fn like (\u0026self, other: \u0026Self) -\u003e bool { self == other } }\n\nforr! { $gens:inner in [(), (A,), (A, B), (A, B, C),\n                        (A, B, C, D), (A, B, C, D, E)] $*\n    // $idx is special this would be like (idx, gen) = [...].enumerate()\n    forr! { $idx:idx, $gen:expr in [$gens] $:\n        impl\u003c$($gen: Like,)*\u003e Like for ($gens) {\n            fn like(\u0026self, other: \u0026Self) -\u003e bool {\n                $(self.$idx.like(\u0026other.$idx)\u0026\u0026)* true\n            }\n        }\n    }\n}\n\nassert!((1, 3).like(\u0026(1, 3)));\nassert!((1,).like(\u0026(1,)));\nassert!(().like(\u0026()));\n```\n\nWith macro-rules this would be:\n\n```rust\nmacro_rules! impl_like_for_tuples {\n    [$(($gen:ident, $idx:tt), $(($gens:ident, $idxs:tt)),*)?$(,)?] =\u003e {\n        impl$(\u003c$gen: Like, $($gens: Like),*\u003e)? Like for ($($gen, $($gens),*)?) {\n            fn like(\u0026self, other: \u0026Self) -\u003e bool {\n                $(self.$idx.like(\u0026other.$idx) \u0026\u0026)?\n                $($(self.$idxs.like(\u0026other.$idxs) \u0026\u0026)*)?\n                true\n            }\n        }\n        $(impl_like_for_tuples![$(($gens, $idxs)),*,];)?\n    }\n}\nimpl_like_for_tuples![(E, 4), (D, 3), (C, 2), (B, 1), (A, 0)];\n\nassert!((1, 3).like(\u0026(1, 3)));\nassert!((1,).like(\u0026(1,)));\nassert!(().like(\u0026()));\n```\n\nGranted in this example it is not a lot more complicated, and adding more\ntuple variants actually requires less code. But it took me quite a few more\ntrys getting it to work correctly. (If you don't count me implementing this\nwhole library for the first one.)\n\n### Usage\n\nThe first part of the invocation is the pattern, similar to a normal `for`\nloop in rust. Here you can use either a [single\nvariable](#single-variable-binding) i.e. `$name:type` or a [tuple\nbinding](#tuple-binding) `($name:type, $nbme:type, ...)`. There can\noptionally be [non consuming patterns](#non-consuming-patterns) specified\nbefore or after, currently that includes only [`:idx`](#idx).\n\nThis is followed by the keyword `in`, an array literal `[...]` containing\nthe tokens to iterate and the [body](#body) marked with either `$*` or `$:`.\n\n#### Single variable binding\n`$` [`name`](#names) `:` [`type`](#types)\n```rust\nforr! { $val:expr in [1, 2 + 4, 20]\n```\n`$val` will be `1`, `2 + 4` and `20`.\n\n#### Tuple binding\n`(` [`$name:type`](#single-variable-binding), ... `)`\n```rust\nforr! { ($val:expr, $vbl:ty) in [(1, i32), (Ok(2 + 4), Result\u003cu8, ()\u003e), (20.0, f32)]\n```\n`$val` will be `1`, `Ok(2 + 4)` and `20.0`.\n\n`$vbl` will be `i32`, `Result\u003cu8, ()\u003e` and `f32`.\n\n#### Non consuming patterns\n\nNon consuming patterns can be specified before or after the consuming\npatterns or inside if using a tuple binding.\n\nCurrently, only [`:idx`](#idx) is supported\n```rust\nforr! { $val:expr, $i:idx in [1, 2]\nforr! { $i:idx, $val:expr in [1, 2]\nforr! { $i:idx, ($val:expr, $vbl:ty) in [(1, i32), (2, i32)]\nforr! { ($val:expr, $vbl:ty), $i:idx in [(1, i32), (2, i32)]\nforr! { ($val:expr, $i:idx, $vbl:ty) in [(1, i32), (2, i32)]\n```\n`$val` will be `1` and `2`\n\n`$i` will be `0` and `1`\n\n`$vbl` will be `i32`\n\n#### Body\n\nThe body can be in two different modes. When it is initialized with `$*` the\nwhole body is repeated similar to a normal for loop. Is it started with\n`$:`, the body will behave more like macro expansion using `$()*` for\nrepetition. In both cases there is special handling for [optional\nvalues](#optional values) when placed inside `$()?` the innermost such group\nis only added if the value is present.\n\n##### `$*` outer repetition\n\nIn the tokens following the `$*` every occurrence of a `$ident` where the\nident matches one of the declared variables is replaced with the\ncorresponding value.\n```rust\nforr! {$val:expr in [(1, \"a\", true)] $*\n    assert_eq!($val, $val);\n}\n```\nwill expand to\n```rust\nassert_eq!(1, 1);\nassert_eq!(\"a\", \"a\");\nassert_eq!(true, true);\n```\n\n##### `$:` inner repetition\n\n`$:` allows to have non repeated code surrounding the expansion, mainly\nuseful for cases where a macro would not be allowed.\n\n```rust\nforr! {($pat:expr, $res:expr) in [(0, true), (1, false), (2.., true)] $:\n    match 1u8 {\n        $($pat =\u003e $res,)*\n    }\n}\n```\nWithout the inner repetition this would not be possible, as macros are not\nallowed as the body of a `match`.\n```rust\nmatch 1 {\n   forr! {($pat:expr, $res:expr) in [(0, true), (1, false), (2.., true)] $*\n       $pat =\u003e $res\n   }\n}\n```\n\n### Names\nAny valid rust idents including keywords can be used to name variables. Note\nthat shadowing does not work, i.e. an inner `forr!` needs to use different\nnames.\n\n### Types\n#### `:expr`\nAs this uses [`TokenParser::next_expression()`](https://docs.rs/proc-macro-utils/latest/proc_macro_utils/struct.TokenParser.html#method.next_expression) this will allow anything\nthat matches the `,` rules for expressions i.e. it cannot contain `;` and no\n`,` outside turbofishes for Types/Generics (`HashMap::\u003cA, B\u003e`).\n\n#### `:ty`\nAs this uses [`TokenParser::next_type()`](https://docs.rs/proc-macro-utils/latest/proc_macro_utils/struct.TokenParser.html#method.next_type) this will allow anything\nthat matches the `,` rules for types i.e. it cannot contain `;` and no\n`,` outside `\u003c\u003e` for Generics (`HashMap\u003cA, B\u003e`) and unopened closing `\u003e`.\n\n#### `:tt`\nCurrently matches exactly one [`proc_macro::TokenTree`](https://doc.rust-lang.org/beta/proc_macro/enum.TokenTree.html), but the plan is to extend this to what [`macro_rule!`'s `:tt` matches](https://doc.rust-lang.org/reference/macros-by-example.html#metavariables).\n\n#### `:inner`\nThe most versatile type, allowing arbitrary tokens wrapped by any bracket\n`[`, `{` or `(` ... `)}]`.\n\n#### `:idx`\n[Non consuming pattern](#non-consuming-patterns) that will contain the\ncurrent index.\n\n## `iff!`\n\nAims to be an alternative version of\n[`#[cfg(...)]`](https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg-attribute)\nable to conditionally enable any rust tokens and being able to, e.g. compare\ntokens, but it is not able to be conditional other actual `cfg` or features.\n\n```rust\niff! { true \u0026\u0026 false $:\n    compile_error!(\"This is not expanded\")\n}\n\niff! { true || false $:\n    compile_error!(\"This is expanded\")\n}\n```\n\nOn top of the basic boolean operations (`!`, `\u0026\u0026`, `||`) there are some\nfunctions:\n\n- `empty(\u003ctokens\u003e)` tests if `\u003ctokens\u003e` is empty.\n- `equals(\u003clhs\u003e)(\u003crhs\u003e)` tests if `\u003clhs\u003e` is equal to `\u003crhs\u003e`.\n\n```rust\niff! { empty() $:\n    compile_error!(\"This is expanded\")\n}\niff! { empty(something) $:\n    compile_error!(\"This is not expanded\")\n}\niff! { equals(something)(another thing) $:\n    compile_error!(\"Neither is this\")\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmodprog%2Fforr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmodprog%2Fforr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmodprog%2Fforr/lists"}