{"id":50618844,"url":"https://github.com/dannote/rustq","last_synced_at":"2026-06-07T10:00:56.526Z","repository":{"id":362290349,"uuid":"1255028663","full_name":"dannote/rustq","owner":"dannote","description":"Rust template quasiquoting and code generation for Elixir","archived":false,"fork":false,"pushed_at":"2026-06-06T07:18:11.000Z","size":191,"stargazers_count":5,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-06-06T09:23:07.889Z","etag":null,"topics":["codegen","elixir","metaprogramming","nif","quasiquote","rust","rustler"],"latest_commit_sha":null,"homepage":"https://hex.pm/packages/rustq","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dannote.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-31T10:03:54.000Z","updated_at":"2026-06-06T07:18:14.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/dannote/rustq","commit_stats":null,"previous_names":["dannote/rustq"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/dannote/rustq","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dannote%2Frustq","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dannote%2Frustq/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dannote%2Frustq/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dannote%2Frustq/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dannote","download_url":"https://codeload.github.com/dannote/rustq/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dannote%2Frustq/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34016490,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-07T02:00:07.652Z","response_time":124,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["codegen","elixir","metaprogramming","nif","quasiquote","rust","rustler"],"created_at":"2026-06-06T09:00:39.209Z","updated_at":"2026-06-07T10:00:56.511Z","avatar_url":"https://github.com/dannote.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# RustQ\n\nRustQ is Rust template quasiquoting for Elixir. It helps Elixir projects generate\nRust from real `.rs` templates instead of building Rust strings by hand.\n\nUse it for Rustler projects, schema-driven NIF surfaces, or any codegen where you\nwant Rust syntax highlighting, Rust parsing, formatting, and AST-aware\nplaceholder replacement.\n\n## Installation\n\nAdd RustQ to `mix.exs`:\n\n```elixir\n{:rustq, \"~\u003e 0.1\", only: [:dev, :test], runtime: false}\n```\n\nRustQ compiles a Rustler NIF at generation time, so Rust/Cargo must be available\nwhere `mix rustq.gen` or your own codegen task runs.\n\n## Generate from real Rust templates\n\nTemplates are ordinary Rust with parseable placeholder forms:\n\n```elixir\nuse RustQ.Sigil\nalias RustQ.Rust\n\ntemplate = ~R\"\"\"\npub struct __rq_Resource {\n    __rq_fields: (),\n}\n\nimpl __rq_Resource {\n    __rq_methods!();\n\n    pub fn table() -\u003e \u0026'static str {\n        __rq_table_name!()\n    }\n}\n\"\"\"\n\ncode =\n  template\n  |\u003e RustQ.parse!(\"resource.rs\")\n  |\u003e RustQ.bind(Resource: :User, table_name: {:literal, \"users\"})\n  |\u003e RustQ.splice(:fields, [\n    Rust.field(:id, :i64, vis: :pub),\n    Rust.field(:name, :String, vis: :pub)\n  ])\n  |\u003e RustQ.splice(:methods, [\n    Rust.fn(:new,\n      vis: :pub,\n      args: [id: :i64, name: :String],\n      returns: :Self,\n      body: \"Self { id, name }\"\n    )\n  ])\n  |\u003e RustQ.codegen!()\n```\n\nFor file templates:\n\n```elixir\nRustQ.render_file!(\"priv/templates/resource.rs\",\n  bind: [Resource: :User],\n  splice: [fields: [RustQ.Rust.field(:id, :i64, vis: :pub)]]\n)\n```\n\n## Placeholder forms\n\nRustQ placeholders use the visually distinct `__rq_` prefix. The exact shape\nmatches the Rust syntax position, but the name is consistent with the Elixir\n`bind:` or `splice:` key:\n\n- `__rq_Name` — identifier, type path, or lifetime replacement.\n- `__rq_value!()` — expression or type replacement.\n- `__rq_items!();` — item splice point.\n- `__rq_methods!();` — impl-item splice point.\n- `__rq_body!();` — statement splice point.\n- `__rq_arms =\u003e unreachable!(),` — match-arm splice point.\n- `__rq_fields: (),` — struct-field splice point.\n- `fn target(__rq_args: ()) {}` — function-argument splice point.\n\n## Rust builders\n\n`RustQ.Rust` provides small Elixir builders for common Rust fragments:\n\n```elixir\nalias RustQ.Rust\n\nitems = [\n  Rust.use([:std, :sync, :OnceLock]),\n  Rust.const(:TABLE, {:ref, :str}, Rust.expr(Rust.literal(\"users\")), vis: :pub),\n  Rust.struct(:User,\n    vis: :pub,\n    derive: [:Clone, :Debug],\n    fields: [Rust.field(:id, :i64, vis: :pub)]\n  )\n]\n```\n\nUse `Rust.raw/1`, `Rust.item/1`, `Rust.impl_item/1`, `Rust.stmt/1`,\n`Rust.expr/1`, and `Rust.arm/1` when hand-written Rust is clearer than a builder.\n\n## Rustler helpers\n\n`RustQ.Rustler` generates common Rustler code as Rust fragments:\n\n```elixir\nRustQ.Rustler.atoms([:ok, :error, {\"r#type\", \"type\"}])\nRustQ.Rustler.cached_atoms([:ok, node_changes: \"nodeChanges\"])\n\nRustQ.Rustler.nif(:add,\n  args: [a: :i64, b: :i64],\n  returns: :i64,\n  body: \"a + b\"\n)\n\nRustQ.Rustler.nif_exports(\n  render_png: [\n    args: [env: \"Env\u003c'a\u003e\", batch: \"Term\u003c'a\u003e\"],\n    returns: \"NifResult\u003cTerm\u003c'a\u003e\u003e\",\n    lifetime: :a,\n    schedule: :dirty_cpu\n  ]\n)\n\nRustQ.Rustler.term_helpers(type_key: \"atoms::r#type()\")\nRustQ.Rustler.term_decoder(:ProgramInput,\n  fields: [\n    body: [type: {:vec, \"Term\u003c'a\u003e\"}, key: \"atoms::body()\", required: true]\n  ]\n)\n\nRustQ.Rustler.resource_handle(:EncodedImage,\n  fields: [bytes: \"Vec\u003cu8\u003e\"],\n  handle_field: \"ref\"\n)\n```\n\nSafe term builders use `Term\u003c'a\u003e`:\n\n```elixir\nRustQ.Rustler.term_builders(include: [:map_from_terms, :struct_from_terms])\n```\n\nLow-level raw `NIF_TERM` helpers are explicit:\n\n```elixir\nRustQ.Rustler.nif_term_builders(include: [:map_from_nif_terms, :struct_from_nif_terms])\n```\n\n## Rustler schema DSL\n\nFor larger Elixir struct surfaces, define a schema once and generate Rust NIF\nstructs plus tagged enums:\n\n```elixir\ndefmodule MyApp.Codegen.ContentSchema do\n  use RustQ.Rustler.Schema\n\n  schema MyApp.Content do\n    default_attrs [\"allow(dead_code)\"]\n\n    node Text do\n      field :text, :String\n      field :size, {:option, :String}\n    end\n\n    node Paragraph do\n      field :body, {:vec, Content}\n    end\n\n    node Enum, rust: :ExEnum, module: MyApp.Content.EnumList do\n      field :children, {:vec, Content}\n    end\n\n    tagged_enum Content do\n      variants :all\n      unknown :unknown_content_variant\n    end\n  end\nend\n```\n\nOptionality is part of the Rust type (`{:option, :String}`), not a separate\nboolean flag.\n\n## Generated files with `rustq.exs`\n\nCreate `rustq.exs` in your project root:\n\n```elixir\nuse RustQ.Config\n\nalias RustQ.Rustler\n\nrequire_file \"lib/my_app/codegen/content_schema.ex\"\n\nrust \"native/my_nif/src/generated_term_helpers.rs\" do\n  Rustler.term_helpers(type_key: \"atoms::r#type()\")\nend\n\nrust \"native/my_nif/src/generated_content.rs\" do\n  MyApp.Codegen.ContentSchema.rust_items()\nend\n```\n\nThe manifest is ordinary Elixir, so use aliases, helper functions, modules, and\nmacros to keep project-specific codegen readable.\n\nThen run:\n\n```sh\nmix rustq.gen\nmix rustq.gen --check\nmix rustq.gen term_helpers\n```\n\nPath-only targets infer their name from the file name and strip a leading\n`generated_`, so `generated_term_helpers.rs` is selectable as `term_helpers`.\n\nUse `mix rustq.gen --check` in CI to fail when generated files are stale.\n\n## Fragment validation\n\nYou can validate individual Rust fragments in the same contexts RustQ splices:\n\n```elixir\nRustQ.valid_fragment?(:field, \"pub id: i64\")\nRustQ.parse_fragment!(:arm, RustQ.Rust.arm(\"Some(value)\", \"value\"))\n```\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdannote%2Frustq","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdannote%2Frustq","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdannote%2Frustq/lists"}