RustQ

Hex.pmDocumentation

Write native Elixir in Elixir. RustQ turns typed, Elixir-shaped code into readable Rust and can generate, compile, and load a complete Rustler NIF without checked-in bridge Rust.

defmodule MyApp.Native do
use RustQ.Native, crates: [crc32fast: "1"]
alias RustQ.Type, as: R
@type point :: %{required(:x) => float(), required(:y) => float()}
@spec checksum(String.t()) :: R.u32()
defnif checksum(value), do: Crc32fast.hash(value.as_bytes())
@spec scale(point(), float()) :: point()
defnif scale(point, factor) do
%{x: point.x * factor, y: point.y * factor}
end
end

That module is the bridge. RustQ derives the Cargo crate, Rustler entrypoints, Elixir stubs, boundary codecs, initialization, build, and native loading. There is no .rs file, Cargo.toml, atom registry, or duplicated signature list to maintain.

Typed Elixir in, Rust out

defrust uses the Elixir typespec as the Rust signature source of truth, then lowers Elixir semantics rather than substituting text:

defmodule MyApp.Generated do
use RustQ.Meta
@spec sum_squares([integer()]) :: integer()
defrust sum_squares([]), do: 0
defrust sum_squares([head | tail]), do: head * head + sum_squares(tail)
end

The list and integer types become Vec<i64> and i64; clauses and list patterns become a Rust match:

fn sum_squares(arg1: Vec<i64>) -> i64 {
match arg1.as_slice() {
[] => 0,
[head, tail @ ..] => head * head + sum_squares(tail.to_vec()),
}
}

RustQ.Type extends normal typespecs with Rust-specific precision such as references, fixed-width numbers, lifetimes, slices, options, results, and NifResult. The same lowerer powers defnif, which additionally derives the BEAM boundary and Rustler attribute. It understands guards, structs, typed maps, comprehensions, closures, and a semantics-preserving subset of Kernel, Enum, List, Map, String, Tuple, and Range.

Why RustQ

Native Elixir libraries usually repeat the same facts across Elixir stubs, Rust functions, Rustler codecs, Cargo configuration, and code generators. RustQ keeps those facts structural and close to their source:

RustQ is for bridge and generator code. Domain-heavy parsers, renderers, and algorithms can stay in Rust while RustQ removes the repetitive boundary glue.

Installation

def deps do
[
{:rustq, "~> 1.0.0-rc.3", runtime: false}
]
end

Projects that only run checked-in generators during development may use only: [:dev, :test]. Rust and Cargo must be available wherever native modules or generators compile.

Choose the right workflow

You want to…Start with
Build and load a NIF without handwritten bridge Rustuse RustQ.Native and defnif
Generate Rust helpers from typed Elixiruse RustQ.Meta and defrust
Integrate an existing or precompiled crateRustQ.Native, build: false, load: false
Read callable metadata from real Rustrust_sources, rust_packages, and RustQ.Syn
Generate declarations from schemasRustQ AST builders and ordinary Elixir macros
Keep generated Rust checked inrustq.exs and mix rustq.gen --check
Generate around substantial handwritten Rustparseable .rs templates and structural splices

Rusty-Elixir is intentionally Elixir-shaped rather than fake Rust syntax. When a construct cannot be expressed honestly in Elixir, use RustQ AST. Raw Rust tokens are the final, local escape hatch—not the default authoring style.

Documentation

Agent skill

RustQ ships SKILL.md, an operational guide for coding agents that work on RustQ-powered bridges and generators. Give it to an agent before it writes generated Rust; it captures the inference-first, AST-first authoring order and the important escape boundaries.

Development

mix deps.get
mix ci

License

MIT