RustQ.Native generates, builds, and loads a Rustler crate from typed
Rusty-Elixir. It is the default starting point when RustQ should own an ordinary
NIF boundary.
defmodule MyApp.Native do
use RustQ.Native, crates: [crc32fast: "1"]
alias RustQ.Type, as: R
@spec checksum(String.t()) :: R.u32()
defnif checksum(value), do: Crc32fast.hash(value.as_bytes())
endThe module above needs no checked-in .rs file, Cargo.toml, NIF registry,
Elixir stub module, or loader. RustQ generates those pieces under the Mix build
directory and keeps the generated crate available for inspection.
For authoring conventions and inference, read Using RustQ Well. This guide focuses on crate ownership and the NIF boundary.
Public entrypoints and helpers
Use each function form for one responsibility:
defnif— public BEAM↔Rust entrypointdefrust— generated public Rust helperdefrustp— generated private Rust helper
All three use real @specs. Ordinary Elixir macros are the preferred way to
reuse Rusty-Elixir source.
@spec add(integer(), integer()) :: integer()
defnif add(left, right), do: add_impl(left, right)
@spec add_impl(integer(), integer()) :: integer()
defrustp add_impl(left, right), do: left + rightWhat RustQ owns
For a normal RustQ.Native module, RustQ owns:
- the generated Cargo package and source
- declared Cargo dependencies
- Rustler attributes and initialization
- Elixir exports and native loading
- directional argument and return codecs
- generated atoms, structural types, and resources
- native compilation and installation into the application
Use crates: when generated code calls an external crate:
use RustQ.Native,
crates: [
crc32fast: "1",
serde_json: [version: "1", features: ["preserve_order"]]
]RustQ derives Rust module aliases from crate names. Cargo versions and features remain explicit because they are release policy, not inference.
Boundary types
RustQ derives directional codecs from @spec and @type. Supported boundary
families include:
- integers, floats, booleans, atoms, strings, and binaries
- tuples and homogeneous lists
- options and tagged results
- typed maps and Elixir structs
- unit enums and structural unions
- Elixir exceptions
- explicitly declared resources
Elixir-facing, Rust-internal, NIF-input, and NIF-output representations are related but not assumed identical. For example, an Elixir binary can decode as a borrowed Rustler binary, be used internally as bytes, and encode from owned bytes.
A structural map type can own the codec for a NIF value:
@type point :: %{required(:x) => float(), required(:y) => float()}
@spec scale(point(), float()) :: point()
defnif scale(point, factor) do
%{x: point.x * factor, y: point.y * factor}
endResources
Wrap a structural state type with R.resource/1 to derive registration and the
ResourceArc boundary:
@type counter_state :: %{required(:value) => integer()}
@type counter :: R.resource(counter_state())
@spec new_counter(integer()) :: counter()
defnif new_counter(value), do: %{value: value}
@spec counter_value(counter()) :: integer()
defnif counter_value(counter), do: counter.valueR.resource/1 does not choose mutation, synchronization, ownership, or thread
safety. Those decisions remain explicit and may require a deliberately designed
state type or nearby handwritten Rust.
Scheduling
A defnif uses the normal BEAM scheduler by default. Mark only entrypoints that
need dirty scheduling:
@nif schedule: :dirty_cpu
@spec resize_image(binary()) :: binary()
defnif resize_image(image), do: resize_impl(image)
@nif schedule: :dirty_io
@spec read_device(String.t()) :: binary()
defnif read_device(path), do: read_device_impl(path)Use :dirty_cpu for long-running CPU-bound work and :dirty_io for native work
that may block on IO. The attribute applies only to the declaration immediately
following it. RustQ renders the Rustler "DirtyCpu" and "DirtyIo" options;
those strings are accepted directly, but atoms are the preferred spelling.
RustQ does not infer scheduling from a function body. Short NIFs should normally stay on the normal scheduler.
Existing and precompiled crates
An existing crate can keep Cargo, initialization, loading, and release ownership while RustQ prepares ABI items:
defmodule MyApp.NativeItems do
use RustQ.Native,
build: false,
load: false,
rust_sources: ["native/my_app/src/lib.rs"]
alias RustQ.Type, as: R
@spec decode(term()) :: R.nif_result(term())
defnif decode(value), do: decode_impl(nif_env(), value)
endRustQ.Native.items/1 returns the prepared functions, codecs, and resource
items for the owning crate to splice. RustQ.Native.source/1 returns the same
prepared source when an external system needs text.
nif_env/0 injects Env<'a> into generated Rust without adding an argument to
the public BEAM function. Use this mode for source-built or precompiled domain
crates that should not depend on RustQ in production.
Supported Rusty-Elixir
defnif and defrust share the same lowering surface. It includes multiple
clauses, patterns, guards, recursion, case, conditionals, with,
comprehensions, closures, pipelines, ordinary macro expansion, and a
semantics-preserving subset of Kernel, Enum, List, typed Map, String, Tuple, and
Range operations.
The subset is intentionally semantic, not name-based. RustQ rejects operations
that cannot preserve Elixir behavior instead of silently choosing a similar
Rust method. Dynamic or descending ranges, unrestricted dynamic maps,
grapheme-counting String.length/1, process semantics, full protocols, async
Rust, IO/network/process APIs, and the complete bitstring grammar remain
explicit adapters or Rust boundaries.
See Using RustQ Well for the authoring ladder and
inference rules. The API reference for RustQ.Meta documents individual forms.
Testing
Use the packaged assertions instead of searching build directories:
use RustQ.Test, async: true
assert rust_source!(MyApp.Native, :add_impl) =~ "fn add_impl"
assert rust_source!(MyApp.Native, :add) =~ ~r/fn add/
assert nif_exported?(MyApp.Native, :add, 2)
assert RustQ.valid?(rust_source!(MyApp.Native), "my_app_native.rs")rust_source!/2 returns one focused function for ordinary ExUnit assertions,
while rust_source!/1 returns the complete generated module.
nif_exported?/3 checks both the Elixir export and generated Rustler attribute.
Use RustQ.valid?/2 when the test specifically needs Rust syntax validation.
Explicit policy checklist
Before shipping a native module, review:
- dependency versions and Cargo features
- scheduler choice and blocking behavior
- resource ownership and thread safety
- unsafe operations
- lossy conversions and custom adapters
- platform linking
- precompiled targets and artifact hosting
Zero handwritten bridge Rust means RustQ derives repetitive structure. It does not mean RustQ guesses safety or deployment decisions.