Current section

Files

Jump to
rustq lib rustq rustler.ex
Raw

lib/rustq/rustler.ex

defmodule RustQ.Rustler do
@moduledoc """
Builders for common Rustler NIF code.
`RustQ.Rustler` returns `RustQ.Rust` fragments that can be spliced into real
Rust templates or declared in `rustq.exs` with `rust_items/2`.
Use the safe helpers by default. Helpers that work with raw
`rustler::wrapper::NIF_TERM` are named with the `nif_term_` prefix so unsafe
code stays explicit.
"""
alias RustQ.Rust
alias RustQ.Rust.AST.Builder, as: A
alias RustQ.Rustler.{
Atom,
Nif,
Opts,
Resource,
Schema,
TaggedEnum,
Term
}
@doc """
Builds a Rustler NIF function declaration.
Options include `:args`, `:returns`, `:body`, `:vis`, and `:schedule`.
"""
@spec nif(atom() | String.t(), keyword()) :: Rust.Function.t()
def nif(name, opts \\ []) do
name
|> Rust.fn(
args: Keyword.get(opts, :args, []),
returns: Keyword.get(opts, :returns),
body: Keyword.get(opts, :body, ""),
vis: Keyword.get(opts, :vis)
)
|> Rust.attr(Nif.nif_attr(opts))
end
@doc """
Builds exported Rustler NIF functions that delegate to implementation functions.
This is useful when the exported Rustler surface is repetitive but the
implementation bodies should stay handwritten.
RustQ.Rustler.nif_exports([
render_png: [
args: [env: "Env<'a>", batch: "Term<'a>"],
returns: "NifResult<Term<'a>>",
lifetime: :a,
schedule: :dirty_cpu
]
])
Generates a `#[rustler::nif] fn render_png(...) { render_png_impl(...) }`
export. Pass `:impl` to override the implementation function name.
"""
@spec nif_exports([{atom() | String.t(), keyword()}]) :: [Rust.Function.t()]
def nif_exports(specs), do: Nif.exports(specs)
@doc """
Builds exported NIF wrappers whose signatures are derived structurally from
handwritten Rust implementation functions.
Implementation functions default to the export name with an `_impl` suffix.
The manifest owns only boundary policy such as scheduling and attributes.
"""
@spec nif_exports_from_source(Path.t(), [{atom() | String.t(), keyword()}], keyword()) ::
[Rust.Function.t()]
def nif_exports_from_source(path, specs, defaults \\ []),
do: Nif.exports_from_source(path, specs, defaults)
@doc """
Builds an Elixir macro module containing NIF-not-loaded stubs whose names and
arities are derived from Rust implementation signatures. Rustler `Env`
arguments are excluded from Elixir arity.
"""
@spec nif_stubs_from_source(Path.t(), [{atom() | String.t(), keyword()}], module(), keyword()) ::
String.t()
def nif_stubs_from_source(path, specs, module, defaults \\ []),
do: Nif.stubs_from_source(path, specs, module, defaults)
@doc """
Builds an Elixir NIF stub macro module from mixed `RustQ.Syn.Function` and
RustQ AST function metadata. Entries may be `{export_name, function}` tuples
when the exported name differs from the implementation name.
"""
@spec nif_stubs_from_functions([term()], module()) :: String.t()
def nif_stubs_from_functions(functions, module),
do: Nif.stubs_from_functions(functions, module)
@doc """
Builds a single exported Rustler NIF function.
"""
@spec nif_export(atom() | String.t(), keyword()) :: Rust.Function.t()
def nif_export(name, opts), do: Nif.export(name, opts)
@doc """
Builds the `rustler::init!` declaration for an Elixir module.
"""
@spec init(module() | String.t()) :: Rust.Fragment.t()
def init(module) when is_atom(module), do: init(Elixir.Atom.to_string(module))
def init(module) when is_binary(module),
do: Rust.ast_item(A.macro_item_call([:rustler, :init], literal: module))
@doc """
Builds a `rustler::atoms!` block.
"""
@spec atoms([atom() | String.t() | {atom() | String.t(), String.t()}], keyword()) ::
Rust.Fragment.t()
def atoms(atoms, opts \\ []), do: Atom.declaration(atoms, opts)
@doc """
Builds a decoder from Rustler atoms into a Rust enum/value type.
RustQ.Rustler.atom_decoder(:decode_blend_mode,
returns: :BlendMode,
cases: [src_over: "BlendMode::SrcOver", multiply: "BlendMode::Multiply"]
)
The generated function returns `NifResult<returns>` by default. Pass `:result`
to override the full return type, `:input` to override the input type, `:atoms`
to override the atoms module, and `:unknown` to override the fallback branch.
"""
@spec atom_decoder(atom() | String.t(), keyword()) :: Rust.Fragment.t()
def atom_decoder(name, opts), do: Atom.decoder(name, opts)
@doc """
Builds a function that dispatches on an atom expression.
RustQ.Rustler.atom_dispatch(:draw_command,
args: [surface: "&mut Surface", command: "Term<'a>"],
on: "command.map_get(atoms::op())?.decode::<Atom>()?",
cases: [rect: "draw_rect(surface, command)"],
unknown: "Ok(())"
)
This is intentionally generic: use it for command dispatch, tagged map
dispatch, AST node dispatch, or any other Rustler atom switch.
"""
@spec atom_dispatch(atom() | String.t(), keyword()) :: Rust.Fragment.t()
def atom_dispatch(name, opts), do: Atom.dispatch(name, opts)
@doc """
Builds keyword/options helper functions over `&[(Atom, Term<'a>)]`.
By default this includes `decode_opts`, `decode_args`, `opt_term`,
`opt_f32`, `opt_f32_option`, `opt_f32_default`, `opt_bool_option`, and
`opt_atom_option`. Pass `:include` or `:exclude` to select helpers.
RustQ.Rustler.opts_helpers(include: [:decode_opts, :decode_args, :opt_term])
`decode_opts` extracts from `atoms::opts()` by default. Pass `:key` to use a
different map key expression. `decode_args` extracts from `atoms::args()` by
default; pass `:args_key` to use a different map key expression.
"""
@spec opts_helpers(keyword()) :: [Rust.Fragment.t()]
def opts_helpers(opts \\ []), do: Opts.helpers(opts)
@doc """
Builds cached atom helper functions backed by `OnceLock<Atom>`.
"""
@spec cached_atoms([atom() | String.t() | {atom() | String.t(), String.t()}], keyword()) :: [
Rust.Fragment.t()
]
def cached_atoms(atoms, opts \\ []), do: Atom.cached(atoms, opts)
@doc """
Builds a Rust struct deriving `NifStruct` for an Elixir struct module.
"""
@spec nif_struct(atom() | String.t(), module() | String.t(), keyword()) :: Rust.Fragment.t()
def nif_struct(name, module, opts \\ []), do: Nif.struct(name, module, opts)
@doc """
Returns Rust items for a `RustQ.Rustler.Schema` schema.
"""
@spec schema_items(Schema.t()) :: [Rust.Fragment.t()]
def schema_items(schema), do: Schema.rust_items(schema)
@doc """
Builds a Rust enum that decodes tagged Elixir structs.
"""
@spec tagged_enum(atom() | String.t(), keyword()) :: [Rust.Fragment.t()]
def tagged_enum(name, opts), do: TaggedEnum.build(name, opts)
@doc """
Builds raw `NIF_TERM` map/struct helpers.
These helpers generate unsafe Rust and are intended for low-level Rustler code
where raw terms are required. Prefer `term_builders/1` when `Term<'a>` is
available.
"""
@spec nif_term_builders(keyword()) :: [Rust.Fragment.t()]
def nif_term_builders(opts \\ []), do: Nif.term_builders(opts)
@doc """
Builds safe `Term<'a>` map/struct helpers.
"""
@spec term_builders(keyword()) :: [Rust.Fragment.t()]
def term_builders(opts \\ []), do: Term.builders(opts)
@doc """
Builds a decoder function from a Rustler map term into a Rust struct.
"""
@spec term_decoder(atom() | String.t(), keyword()) :: [Rust.Fragment.t()]
def term_decoder(name, opts), do: Term.decoder(name, opts)
@doc """
Builds a `rustler::Encoder` implementation for a struct's atom-keyed fields.
Fields may be atoms or `{key, field}` pairs when the atom key and Rust field
names differ. Pass `:target_lifetimes` for lifetime-bearing adapter structs.
"""
@spec term_encoder(atom() | String.t(), keyword()) :: Rust.Fragment.t()
def term_encoder(name, opts), do: Term.encoder(name, opts)
@doc """
Builds common map/term helper functions used by generated decoders.
"""
@spec term_helpers(keyword()) :: [Rust.Fragment.t()]
def term_helpers(opts \\ []), do: Term.helpers(opts)
@doc """
Builds a Rustler resource struct and resource registration helpers.
"""
@spec resource(atom() | String.t(), keyword()) :: [Rust.Fragment.t()]
def resource(name, opts \\ []), do: Resource.build(name, opts)
@doc """
Builds a Rustler resource plus an Elixir handle decoder.
The generated decoder extracts a `ResourceArc<...>` from a field on an
Elixir-facing handle struct or map. The field defaults to `"ref"`.
RustQ.Rustler.resource_handle(:EncodedImage,
fields: [bytes: "Vec<u8>"],
handle_field: "ref"
)
Pass `:decoder` to override the decoder function name.
"""
@spec resource_handle(atom() | String.t(), keyword()) :: [Rust.Fragment.t()]
def resource_handle(name, opts \\ []), do: Resource.handle(name, opts)
@doc """
Returns the `ResourceArc<...>` Rust type for a resource.
"""
@spec resource_arc(atom() | String.t()) :: String.t()
def resource_arc(name), do: Resource.arc(name)
@doc """
Builds a decoder function for a Rustler resource.
"""
@spec resource_decoder(atom() | String.t(), keyword()) :: Rust.Fragment.t()
def resource_decoder(name, opts \\ []), do: Resource.decode(name, opts)
@doc """
Builds resource initialization code for a Rustler module.
"""
@spec resource_init(atom() | String.t()) :: Rust.Fragment.t()
def resource_init(name), do: Resource.init(name)
@doc """
Builds a type alias for a resource's `ResourceArc` type.
"""
@spec resource_type(atom() | String.t(), keyword()) :: Rust.TypeAlias.t()
def resource_type(name, opts \\ []), do: Resource.type_alias(name, opts)
@doc """
Builds an options struct plus decoder function for keyword/options.
Field specs may be explicit Rust boundary specs:
RustQ.Rustler.opts_decoder(:RectOpts,
fields: [x: [type: :f32, decode: RustQ.Rustler.Decode.opt_decode(:opt_f32, :opts, :x)]]
)
Or structural RustQ types, where the builder derives the Rust boundary type
and decoder from `RustQ.Meta.Type.category/1`:
RustQ.Rustler.opts_decoder(:RectOpts,
fields: [x: [type: RustQ.Spec.type(quote(do: RustQ.Type.f32())), required: true]]
)
Structural external/domain types remain `Term<'a>` at the boundary unless an
explicit `:decode` expression is supplied.
"""
@spec opts_decoder(atom() | String.t(), keyword()) :: [Rust.Fragment.t()]
def opts_decoder(name, opts), do: Opts.decoder(name, opts)
end