1.0.0-rc.19 - 2026-10-26
Added
- What std's methods return is known wherever the receiver's own type
parameters decide it:
name.as_ref()on an&Option<String>is anOption<&String>, andlist.first()anOption<&T>of its items.Type.substitute/2binds a type's parameters. - A list literal whose items share a type is a list of that type.
Fixed
Enum.aton a pipeline stage is the iterator'snth, collecting no list.- A filter over copied tuples matches each by reference, as
|&(set, field)|. Enum.count,length, andbyte_sizestayusizewhere one is expected.- A function a
defrustmacrodefines can name the module's own types in its spec, as adefrustcan. - A fallible call used as the receiver of a method its result lacks, as
decoder.read_bool().encode(env), propagates its error first. - A loop over a slice, or a list behind a reference, reads each item by reference, so passing one on where a reference is expected borrows it once.
1.0.0-rc.18 - 2026-10-26
Added
- A pipeline of
Enumstages runs as one Rust iterator chain:list |> Enum.map(f) |> Enum.find(p)is.map(f).find(p), with no list collected between them, andEnum.count/1on a stage counts as it goes. mix rustq.stdalso derives what std's methods on strings, characters, lists, maps, sets, and options return where the receiver's type parameters do not matter, soname.len()is ausizeandname.trim()a&str.- A comprehension's type is the list, map, or set it collects its body's values into, and a block of expressions lowers wherever an expression does.
Changed
- Whether a value may be taken is decided for each use of it: a pass over the
body records what is still in use after every variable occurrence, as
Elixir scopes it, so a list used again later in the same expression, as
{Map.put(scores, name, 1), scores}, is copied. A parameter ismutwhere the lowered Rust changes it in place. Map.new,MapSet.new,Enum.intointo an empty collection,group_by,uniq, and generators overEnum.with_indexare rewritten into other Rusty-Elixir before lowering, byRustQ.Meta.Desugar.- Pattern, comprehension, path, and mutability lowering are their own modules
beside
RustQ.Meta.Lower. - Each Elixir module's lowering implements
RustQ.Meta.Lower.Stdlib.Lowering, whoseuseimports the helpers they share, and a call goes to the lowering named after its module. Lowering builds Rust withRustQ.Rust.AST.Builder, whoseexpr/1now accepts every RustQ AST node.
1.0.0-rc.17 - 2026-10-25
Added
- A module's
defrustmodmappings come with it under the alias naming it, anddefrustmod __MODULE__, as: [...]maps the module itself, so a library maps the Rust modules its generated code defines once for every module using it. MapSetlowers onHashSets,MapSet.t(item)is aHashSet, andMaplowers onHashMaps as well as structs:get,fetch!,has_key?,put,delete,merge,update/4,keys,values,to_list, andnew, andMapSet.new,put,delete,member?,union,difference,intersection,subset?,disjoint?,size, andto_list. Updates take the collection only at its last use.Enum.into,group_by,with_index,find_index,find_value,reduce_while,sort_by,max_by,min_by,max,min(with an empty fallback),uniq,uniq_by,dedup,split_with,at,slice,take_while,drop_while,zip_with,chunk_every,join, andmap_join. A comprehension overEnum.with_index(list)enumerates the list in place, andinto: HashMap.new()orMapSet.new()collects into that collection where no type is expected.- Rebinding a list, map, or set to its update, as
scores = Map.put(scores, name, 1)orlayers = Enum.sort_by(layers, ...), changes it in place:scores.insert(name, 1);. - A comprehension generator over one value, as
{:some, id} <- [record_id(record)], matches the value beside the item instead of iterating a list made for it.
Changed
Enumfunctions take an owned list only at its last use. A list used afterwards, by the call's closure, or by a value built from it is borrowed, so code using the list again compiles. Questions such asany?andall?always borrow, items kept from a borrowed list are cloned, and numbers and atoms are copied out.- A new value such as
HashSet.new()passed where a reference is expected is borrowed. - A list reached through a reference, as
binding.supersededfor a borrowedbinding, is never taken:Enumborrows it and a comprehension copies its items, as a comprehension also does for an owned list still in use. Enum.filter/2,reject/2,find, andcount/2lower to Rust'sfilterandfind, as clippy prefers tofilter_mapreturning the item; a number or atom item is matched as|&value|.Enumiterates slices withiter().RustQ.Rust.AST.PatRefrenders&pattern.
1.0.0-rc.16 - 2026-10-25
Added
- A RustQ module provides the
.rsfile named as its own file is intemplates/beside it, such astemplates/runtime.rsforruntime.ex: Rust it emits as it is, like a runtime a library ships. Its functions, methods, and types join the module's exports, each type as an Elixir type, and__rustq_items__/0and__rustq_source__/0emit it.use RustQ.Meta, provides: [...]names other files, relative to the module's file. A library no longer needs to publish where its Rust files are. - A RustQ module named through
aliasin a spec, a type, or adefrustbody is imported ascallable_modulesimports one, and calling its Rust function asModule.function(...)lowers to the function's call. Other aliases remain Rust paths.
1.0.0-rc.15 - 2026-10-24
Added
match?(pattern, value)lowers to amatchanswering whether the value matches, sodefrustbodies need not spell it as acasereturningtrueandfalse.
1.0.0-rc.14 - 2026-10-23
Added
RustQ.Meta.AST.macro_call!/3takes RustQ AST for:exprand:tycaptures and renders it, so a generator need not write Rust source strings for them.
Changed
use RustQ.Meta, rust_sources: ...evaluates its paths when the module compiles, so they can come from a module attribute or a function, such as one returning the path of a Rust source a library ships. Relative paths are still relative to the working directory, which a library loaded from another project cannot rely on.defrustmacrorepetition rows separate their captures with commas, in the pattern and in the callsRustQ.Meta.AST.macro_call!/3builds, for every row. RustQ used to lay out a few row shapes, written for kiwi_codec, with=>and:, and fall back to spaces otherwise, whichexprcaptures cannot be followed by.RustQ.Rustler.Term's struct helpers no longer name rustler's raw term type, which rustler 0.39 changed fromrustler::wrapper::NIF_TERMtorustler::sys::ErlNifTerm.cached_struct_keyscachesAtoms,default_struct_valuesreturnsVec<Term>, andmake_struct_from_nif_term_arraysismake_struct_from_arrays, taking atom keys and term values. The same Rust builds on rustler 0.37 to 0.39.RustQ.Nativecrates depend on rustler 0.39 by default, andmix rustq.templates.checkand the suite's generated-Rust check build against it.
Removed
RustQ.Rustler.Nif.raw_term_builders/1. Its helpers calledrustler::wrapper, which rustler 0.39 removed;RustQ.Rustler.Term.builders/1builds the same maps and structs from terms.
Fixed
assign!(index(values, i), call())propagates a fallible call with?when the element type is known, as aletwith a known type does, and makes an ownedvaluesargumentmut. The Rust assigned theResultand changed an immutable argument.- A
defrustinside an itemdefrustmacrokeeps its plain arguments plain. Every argument used to be written as a macro variable,$offset, which the body then referred to asoffset. - Calling a function a
defrustmacronames with an:identcapture calls$inner(...)rather than a function literally namedinner.
1.0.0-rc.13 - 2026-10-23
Added
- A type declared twice in a module is a
:duplicate_typediagnostic naming both lines. RustQ reads types before Elixir rejects the duplicate, and used to follow the later declaration silently; types read from source withRustQ.Specwere never checked at all. RustQ.Spec.declarations/1takes declarations' lines fromquote location: :keepas well as from parsed source.
Changed
- A typespec RustQ has no Rust type for, such as
pid(),keyword(),map(), a barenil, or a literal1or1..10, is an:unsupported_typediagnostic suggesting aRustQ.Typespelling. It used to become a Rust type named after it, which never compiled.any()isTerm, asterm()is. byte()isu8,non_neg_integer()andpos_integer()areu64, andchar()isu32, since it includes surrogate code points Rust'scharcannot hold.pos_integer()admits zero, and rustler'su8decoder truncates out-of-range integers rather than rejecting them.- Which words are Rust keywords, written as raw identifiers, comes from syn rather than a list kept in RustQ, in the native and the text renderer alike.
Fixed
- A function returning
R.nif_result(t)returns a plain value asOk(value), as the NIF returns that value in Elixir. Its Rust returned the bare value, which did not compile; a plain value returned where anR.resultis expected is now a diagnostic asking for{:ok, value}. - A tail
decode_as!in such a function is itsdecode, without the?that made it not compile. :okreturned where an atom is expected is the atom, andOk(())for any result carrying nothing; it used to be()wherever the type was notNifResult<()>.{:some, value}and:nonewhere an option is expected areSome(value)andNone, as patterns read them;{:some, 12.0}used to be a tuple.- A function returning a variable typed as a 3-tuple returned the variable's Elixir AST as a tuple of atoms.
trueandfalserender as raw identifiers wherever a name goes, andmacro_rules!names and groupedusenames are escaped as other names are.- Rendering a node on its own escapes keywords in variables, fields, methods, closure arguments, patterns, and atom names, as rendering it inside an item does.
- A name Rust cannot use as an identifier, such as a field named
self, is a diagnostic carrying syn's message. It used to render invalid Rust, fail with "argument error", or crash the native renderer. - Rendered Rust is checked to parse before it is returned.
RustQ.Rust.AST.Builder.expr(true)builds the literal, not a variable namedtrue.- A
RustQ.Nativecrate using%{key => value}orHashMap.tin a NIF compiles; it importsHashMap. - A public
R.enumtype crosses the NIF boundary as Elixir writes it, as{:circle, 1.0}or:empty, throughrustler::NifTaggedEnum. It had no codec, so a NIF taking or returning one did not compile. - A
defnifwhose spec returnsbinary()returns an Elixir binary. Its bytes are aVec<u8>in Rust, which rustler sends as a list, so the NIF now wraps a*_rustq_binary_implfunction and copies its bytes into a new binary. Abinary()nested in an argument or result still crosses as a list of bytes. gen, which the 2024 edition reserves, renders as a raw identifier.- A path segment named
try, or a variable namedbreak,continue,return, oryield, renders raw. It used to render as the keyword, which parses but does not name the variable.
1.0.0-rc.12 - 2026-10-22
Added
- Lower comprehensions with several generators, filters, and patterns that can
fail to match. As a value, a comprehension becomes an iterator pipeline that
collects into the expected type, so
into:works by the target type; run for its effects, it becomes nestedforloops withif letandif. - Lower
&fun/1captures in any expression, not only insideEnumcalls, and&(&1 + 1)shorthand to a closure, including as anEnummapper. - Lower
container[key]to Rust indexing, borrowing the key for a map, also through a reference to one. - A spec's
%{key => value}with one key type is aHashMap, asHashMap.t(key, value)is. - Match constants such as
Kind.PLAINin patterns, resolved through the module's Rust module aliases, andtrueorfalseinwithclauses. - Add
R.ref/2andR.mut_ref/2for references with a named lifetime, as inR.ref(Record.t(), R.lifetime(:a)), which a function returning a reference borrowed from one of several reference arguments needs. - Add
R.hashable(type)for a declared struct or enum that also derivesPartialEq,Eq, andHash, so it can key a map or set. - Build and match
R.enumvalues with tagged tuples, as in Elixir:{:float, value}isLiteral::Float(value)and:missingisLiteral::Missingwherever the enum is expected or matched, including through a reference and inside a type that refers to itself. - Generate Rust-only types from
@typep: the struct, enum, or alias without the term decoder a@typegets, so it may hold values without a term codec. Lower
%{value | field: new}on a struct to Rust's struct update syntax,Struct { field: new, ..value }, including on the items a comprehension orEnumfunction's closure is given; a struct a closure captures is cloned.- Closures that a comprehension or
Enum.map,flat_map,each,reduce,any?, orall?gives a typed list's items know their type. - A later generator over a list from outside the comprehension copies its
items, so
for x <- xs, y <- yscompiles without cloningysby hand. - Add
R.slice(type, R.lifetime(:a))for a slice with a named lifetime. An atom where an atom-union type such as
:small | :largeis expected names the enum variant, as patterns already did, including fields of astruct_literalwhose struct the module declares.- A module may use the types its
callable_modulesdeclare, written remotely asOther.tree()since Elixir checks local types; they keep their single Rust declaration in the module that declares them. RustQ.Syndescribes traits, asRustQ.Syn.Traitwith their associated types and methods, andRustQ.Syn.Index.traits/1lists them. Impls carry their type parameters' trait bounds and the associated types they define; methods carry their attributes and type parameters' bounds. Impls also carry their trait's structured path astrait_ast, andnegativemarksimpl !Trait.RustQ.Syn.parse(source, unstable: true)reads source that uses unstable syntax which does not change an item's signature, as the Rust standard library does.
Changed
- Which std methods mutate their receiver, return an iterator or an option,
belong to
OptionandResult, or belong to std collections is derived from the Rust standard library's source rather than listed by hand, so lowering knows every stable method, such aspush_back,retain_mut, ornth. - A callable's arguments are
RustQ.Binding.Callable.Argstructs rather than bare maps with the same keys. - Piping into a function the module can call passes the value as its first argument, as in Elixir. It used to call a method of that name on the value.
- An Elixir predicate such as
live?names the Rust functionlive; a module defining both is rejected. - Document every module and function that was hidden from the API reference. Lowering, code generation, native build, and tooling internals are grouped as such, and the user-facing groups list only user-facing modules.
- Stub modules
RustQ.Rustler.Nifgenerates carry a moduledoc, so checked-in generated stubs need regenerating. - The API reference leaves out the tools in
dev/for working on RustQ itself, which the package does not ship.
Fixed
- Elements of a tuple pattern are full patterns, so
{{:some, Kind.PLAIN}, _}matches the constant inside the option. - A pipe into a name the module does not define lowers as that method call,
with the same argument types and borrows; a
letbound to acollect()states what it collects into, aVecunless known. - A binding used through option methods such as
is_somekeeps its option type, a vector's items are not taken for its success type, and methods every collection has, such asis_empty, no longer type a binding as whichever source type also defines them.cloneof a borrowed struct andto_vecare typed. ok(value)lowers the value as the expected success type, sook(:ok)isOk(()); tuple literals are typed when their elements are, so atom and constant patterns match inside them.- Mark
muta tuple-destructured binding that changes, and an owned argument changed through a field or borrowed withmut_ref. A field named like a changing method, such aslayer.swap, does not mark its structmut. - Pushing a call that fails as the function does onto a vector of unknown items propagates it; collecting where a slice is expected collects into a vector and borrows it; an option or result binding nothing downstream unwraps keeps its type.
- Write the Rust Clippy expects: a comprehension over
values.iter()does not callinto_iter()again, one that clones each item iscloned(),into_iter()on a borrowed collection isiter(), and a statementcasewhose other branch does nothing is anif let. - A closure
Option::maporand_thentakes lowers its body as the expected option's value, so tagged tuples and atoms inside it name variants; an option a closure maps, items collected, or an iterator'snextis not wrapped in a secondSome.or,or_else,as_sliceare typed. - Patterns in
with, inside{:some, _}, and on slices take the type of what they match, and a list pattern on a value of unknown type is a slice pattern. - Inference reads a struct literal's fields as the types the struct declares, ignores uses after a pattern binds the same name again, and a comprehension ignores item variables its body does not use.
- A failed native render names atoms matched inside patterns no type resolved.
- A borrowed number, boolean, or tuple of them passed where the value is
expected is copied out with
*, and arithmetic on borrowed numbers is typed as a number;ref(value)is typed as a reference, andtry_foldin an option-returning position is not wrapped inSome. - A comprehension whose collection type is known lowers its body as the item
type, so tagged tuples and atoms in it name variants, and
extendon a vector takes items of its type. - A
withrun for its effects whoseelsedoes nothing lowers to nestedif lets, withtrue <- conditionas anif; variables a slice pattern binds are typed as references to its elements. - Keep the grouping of nested operators:
a and (b or c)rendered asa && b || c, andnot (a or b)as!a || b. Operands are parenthesized by Rust's precedence. - A
condrun for its effects lowers toifstatements, and a comprehension bound in a statement states what it collects into, aVecunless known. - An explicitly borrowed argument is not borrowed again, and a method that
changes its receiver marks it
mutwherever it is called. - Render words Rust reserves for future use, such as
overrideandyield, as raw identifiers, including function and closure arguments and local calls. - Compare two options or results without propagating either side, never
propagate literals, and type arithmetic on a propagated option as its value,
so
some(value + 1)no longer wraps the sum in a second?. - Parenthesize a
?applied to a compound expression; it used to bind only to the last operand. - Mark a variable
mutwhen a statement-level method chain starting with a method that changes it, such asmap.entry(key).or_default().push(value), uses it, and a closure argument its body changes, such as a fold accumulator. An empty map or set bound with a known type states it, as Rust cannot infer its value type throughentry(key).or_default(). - Lower a
withwhoseelseis a lone catch-all to that fallback arm, rather than a single-arm match on the unmatched value, and a borrowed list literal to a borrowed array rather than a vector built for the borrow. - Patterns of unknown type drop the old types of the names they rebind, and a
withclause takes the type of a plain variable it matches, so a rebound value is no longer propagated as the option it came from. - A
withwithoutelsepasses an unmatched{:error, _}ornilon as the function's ownErrorNone, as Elixir returns the unmatched value. - Never propagate with
?in a function or closure returning a plain value such as a vector; the value stays wrapped there. - Type
geton maps and on borrowed vectors,and_then,copied,cloned, and methods reached through a pipe, so an option they return is no longer wrapped in a secondSome. - A function returning nothing runs its last expression as a statement, so a
trailing
iforforno longer becomes a value such as a collected vector. ref([])where a slice is expected borrows an empty array.- A type that refers to itself, such as a tree whose children are trees, names its Rust type in its own fields.
- Mark an owned argument
mutfor any method that changes it in place, such asretainorsort, not onlypushandinsert.
1.0.0-rc.11 - 2026-09-30
Added
- Describe enum variant payloads in
RustQ.Syn.Enum.variant_shapes, a list ofRustQ.Syn.Variantstructs with the variant name, its kind (:unit,:tuple, or:named), its fields, and its doc comments.variantsstill lists variant names. - Report
lifetimesandtype_parametersforRustQ.Syn.StructandRustQ.Syn.Enum, matching the existing function and method metadata. - Add
RustQ.Rustler.Term.encoders_from_source/3andencoder_atoms_from_source/3, which buildTermencoder functions and their atom declarations for structs and enums read from Rust source. They support types owned by another crate and withoutserde. Any reachable type that is not indexed, a wrapper, a scalar, or mapped as external fails generation with a list of the unmapped types, as does a variant tag that would replace a payload field with the same key. Per-type policy covers excluded fields, key and variant renames,with:helpers, andtransparent: truestructs. - Add
RustQ.Syn.Index.structs/1,struct/2, andstruct!/2. - Add
RustQ.Rust.AST.PatternBuilder.tuple/1.
Fixed
- Render Rust keywords used as field names, struct literal and struct pattern
fields, and macro item arguments as raw identifiers (
r#type). Field access such asvalue.typepreviously failed to render. - Type a literal atom such as
:if_nodein a typespec asAtom, like a union of atoms. It was emitted as a Rust type named after the atom, which does not compile.
1.0.0-rc.10 - 2026-09-28
Fixed
- Resolve local type alias references regardless of declaration order. An
alias referenced inside a list, tuple, or map value from an alias that sorts
before it, such as
[plugin()]inlint_input, was emitted as the raw Elixir name (Vec<plugin>) instead of its Rust type (Vec<Plugin>). - Stop passing
fast_path:to JSONCodec, which json_codec 0.3 deprecates, so RustQ compiles without warnings on json_codec 0.1 through 0.3.
1.0.0-rc.9 - 2026-09-06
Added
- Support named fields and attributes on Rust enum variants, and attributes on match arms, enabling feature-gated event declarations and dispatch.
- Add explicit move captures with
move(fn ... end), tuple-pattern closure parameters, and structural closure arguments with optional Rust types. - Add
R.implfor native callable types using ordinary Elixir function signatures, with:fn,:fn_mut, and:fn_oncekinds, additional traits, and explicit lifetime bounds. - Preserve foreign callable-trait parameter and return types, and specialize supported generic method arguments from known receiver and argument types.
- Expand supported source-defined Rust type aliases for receiver inference, including generic aliases and grouped imports. Resolve known standard-library shared pointers, mutex lock results, and guard receivers without guessing from unrelated method names.
Fixed
- Render matching struct-field bindings in shorthand form for Clippy-clean generated patterns while preserving explicit renamed bindings.
- Reject enum variants that mix tuple and named fields.
- Preserve reference and unary-expression precedence in method receivers.
- Infer borrows for dereferenced values and supported
StringandVecclone results, including expressions derived from typed callback parameters. - Keep closure parameter types scoped to the callback instead of inheriting shadowed outer-variable types.
- Omit redundant unit returns, use struct-expression field shorthand, and simplify supported ignored-error result matches for Clippy-clean output.
- Avoid unnecessary mutable bindings for local references while preserving explicit mutation requirements.
- Respect generic-parameter shadowing during alias expansion, refresh aliases after source edits, and diagnose conflicting generic substitutions.
- Reject unsupported lifetime-alias expansion and inconsistent specialization of opaque foreign trait bounds rather than silently changing their meaning. Defer unsupported nested or ambiguous alias diagnostics until expansion requests them.
1.0.0-rc.8 - 2026-09-06
Added
- Add
@cfgfor conditional Rust helper, method, and NIF implementations with shared signatures and separate clause groups. - Support nested attribute metadata, including
not,all, andanypredicates, with typed path, name/value, and list representations. - Select conditional functions with
RustQ.Meta.AST.function!/3and a:cfgoption.
Changed
- Reject ambiguous function selectors instead of choosing the first conditional implementation.
- Diagnose stacked or dangling conditions, duplicate conditions, mixed unconditional/conditional definitions, and inconsistent NIF scheduling policies.
Fixed
- Preserve authored receiver names and lexical shadowing in
defrustimplmethods through a local binding to the Rust receiver. - Gate generated NIF result codecs and implementation helpers consistently with their conditional entrypoints while retaining one Elixir export.
1.0.0-rc.7 - 2026-09-06
Changed
- Reject integer operands for
/with an actionable diagnostic; use explicit floating-point conversion when floating-point division is intended. - Validate generated Cargo dependency names, options, and duplicates, and serialize manifests with a TOML encoder for correct string escaping.
Fixed
- Load generated native libraries correctly on macOS.
- Preserve native result types through NIF boundary encoding, recursive calls, composed calls, closures, and named captures; diagnose generated-name conflicts.
- Preserve expected result types in
condbranches and typedEnum.mapclosures. - Resolve local type aliases in decoding expressions and external struct constructor paths consistently with their remote typespecs.
- Infer the decoded value type for return-position
.decode()and optional return types for checked integer arithmetic methods. - Preserve argument evaluation and infer result propagation for shape queries.
- Refresh callable metadata after provider recompilation and source changes, including same-size edits with unchanged timestamps.
- Render nested conditionals as
else ifwhere appropriate for Clippy-clean output.
Security
- Update maintainer HTTP dependencies to versions without the reported security advisories and remove unused dependency lock entries.
v1.0.0-rc.6 - 2026-07-27
- Infer mutable owned Rust arguments from mutating collection method calls and
render them directly as
mutparameters, including metadata-backed mutable receivers and supportedVecand set/map insertion methods.
v1.0.0-rc.5 - 2026-07-27
- Add focused
RustQ.Meta.ASTselectors for generated Rust items, type items, enums, structs, and inherent or trait implementation blocks.
v1.0.0-rc.4 - 2026-07-27
- Add
defrustimplfor generating inherent and trait Rust implementation methods directly from typed Rusty-Elixir, including&selfand&mut selfreceiver inference and implementation visibility, attributes, and lifetimes.
v1.0.0-rc.3 - 2026-07-17
- Generate optional Rustler term filters with typed
Option::filtercalls for compatibility with Rust 1.97 Clippy. - Highlight fenced Rust examples in generated HexDocs.
v1.0.0-rc.2 - 2026-07-16
- Add composable
RustQ.Test.rust_source!/1,2andnif_exported?/3helpers that preserve ordinary ExUnit assertions and diffs; reorganize the suite around focused source values, parameterized schema/corpus cases, compiled support fixtures, and ExUnit-managed temporary directories. - Decode precise-capturing
impl Trait + use<'a>bounds without relying on pre-existing runtime atoms, as exercised by currentskia-safemetadata. - Funnel compiled
defrustAST access and internal rendering through the documentedRustQ.Meta.ASTandRustQ.Rustboundaries. - Preserve inferred tuple types as structural
RustQ.Rust.AST.TypeTuplenodes instead of raw rendered type strings. - Preserve imported Rust reference lifetimes and fixed-array lengths, lowering
them through structural
TypeRef,TypeArray, andTypePathnodes. - Add structural bare-function types with bound lifetimes, unsafe and ABI qualifiers, named input metadata, variadics, and return types.
- Preserve ordered Rust path generic arguments and
impl Traitbounds in Syn metadata, and lower complete bound lists through structuralTypeImplTrait. - Document
defnifnormal, dirty CPU, and dirty IO scheduler policy and the preferred@nif schedule: :dirty_cpu | :dirty_iospelling. - Replace the README reference manual with a focused project overview and move template, AST, splice, metadata, and validation details into a generation guide.
- Lower supported remote calls in Elixir pipelines and carry expected collection
types through materialized Enum stages, preserving eager semantics while
avoiding ambiguous Rust
collect()inference.
v1.0.0-rc.1 - 2026-07-16
- Define the RustQ 1.x compatibility contract and ship the policy with the package, HexDocs, and agent skill.
- Add
RustQ.Native, publicdefnif, privatedefrustp, generated Cargo crates, native loading, Cargo dependency metadata, and dirty-scheduler normalization for zero-handwritten-bridge-Rust NIFs. - Derive directional Rustler boundaries for scalars, strings, binaries, tuples, lists, options, tagged results, typed maps, Elixir structs and exceptions, structural unions, unit enums, and explicitly declared resources.
- Expand Rusty-Elixir lowering with multiple clauses, recursion, list/map/struct patterns, guards, comprehensions, and structural slice-pattern AST support; normalize semantics-preserving Kernel, Enum, List, typed Map, String, Tuple, and Range calls through focused hidden lowerers with call-specific typing.
- Add external/precompiled-crate native preparation through
use RustQ.Native, build: false, load: false, with publicRustQ.Native.items/1and implicitnif_env/0injection that preserves BEAM arity. - Add packaged
RustQ.TestExUnit assertions for focuseddefrustsource,defnifexports/attributes, and generated Rust validity. - Expose all compiled
defrustfunction AST nodes through the documentedRustQ.Meta.AST.functions/1generator boundary. - Validate documented public APIs, package contents, checked generation, and a formatted, Clippy-clean zero-Rust native crate through structured external consumer fixtures containing no checked-in Rust or Cargo files.
v0.11.1 - 2026-07-14
- Lower named
Enum.map/2function captures and multi-statement closure bodies, and dogfood them for generated native AST collection decoders. - Generate Rustler term decoders structurally, including cached struct keys, and add an AST-backed unsafe block for isolated low-level Rustler operations.
v0.11.0 - 2026-07-13
- Require structural Rust type nodes at the native rendering boundary, normalize builder type inputs, and add structural tuple types.
- Support multiple lifetimes on Rust struct AST nodes, hide the internal AST renderer, and remove duplicate simple-item builder delegates.
- Validate atom-backed Rust identifiers and avoid callable metadata cache locks during parallel compilation.
v0.10.0 - 2026-07-10
- Make
RustQ.Rust.ASTthe canonical generated-Rust representation, including source-derived NIF wrappers with multiple lifetimes, and remove the duplicate legacy Rust builder model. - Reorganize Rustler generation into focused
Nif,Term,Atom,Opts,Resource, andSchemaAPIs with concise wrapper, function-metadata, stub, and encoder naming. - Simplify generator manifests, template rendering, metadata selection, and file-spec APIs; hide implementation modules and document the public API by responsibility.
v0.9.11 - 2026-07-10
- Discover Rustler atom references through a configurable Rust module name, including references nested in macro token trees.
v0.9.10 - 2026-07-10
- Preserve explicit nested
Optionconstructors and inferOption::map(Some)without adding extra wrappers or propagation. - Add a source-generated optional-string map decoder and emit zero-arity Elixir NIF stubs without parentheses.
v0.9.9 - 2026-07-10
- Preserve nested wrapper tuple binding types and lexical pre-shadow types so downstream inference does not add propagation to infallible values.
v0.9.8 - 2026-07-10
- Select map-backed Rust struct items from Rusty-Elixir
@typemetadata with configurable derives, attributes, struct visibility, and field visibility.
v0.9.7 - 2026-07-10
- Add structural schema field and shorthand-field splices for projection-driven Rust struct declarations and literals.
v0.9.6 - 2026-07-10
- Generate one Elixir NIF stub module from mixed Rust source metadata and RustQ
AST functions, including automatic Rustler
Envexclusion.
v0.9.5 - 2026-07-10
- Preserve no-parentheses function-pointer field access in expected-type
positions instead of lowering fields named
decodeas generic Rustler method calls.
v0.9.4 - 2026-07-10
- Add AST-backed Rustler term encoders with nested, conditional, optional, collection, adapter, helper, and fallback field projections.
- Add source-derived Rustler NIF wrappers and Elixir fallback stubs so one manifest and the handwritten Rust implementation signatures determine both sides of the boundary.
- Discover atom references inside Rust macro token trees and document stable, structural Rustler generation workflows.
- Add optional Rustler map decoders and improve option/method result inference.
v0.9.3 - 2026-07-06
- Infer
Option<T>author input for expectedOption<&T>adapter arguments, including fallibleNifResult<Option<T>>values passed toimpl Into<Option<&T>>Rust methods. - Preserve owned option locals while emitting
.as_ref()at the eventual call site to avoid borrowing from temporaries.
v0.9.2 - 2026-07-06
- Improve Rusty-Elixir propagation and borrow inference for fallible option
cases,
some(...),unwrap_or,decode_as!,map_get, fallible reference access, list/slice arguments, loop bindings, and method arguments. - Improve callable metadata lookup for qualified method receivers and add corpus
coverage for
impl Into<Option<T>>argument propagation. - Refresh README, guide, skill, and module docs around inference-first authoring
with less explicit
unwrap!,ref, andmut_refboilerplate.
v0.9.1 - 2026-07-06
- Improve expected-input inference for callable arguments, including fallible
casescrutinees, associated calls, and slice/Vecborrow adaptation. - Add corpus coverage for semantic
defrustmacroitem calls and recent propagation/borrow inference regressions.
v0.9.0 - 2026-07-06
This release is a large Rusty-Elixir self-hosting and downstream dogfooding release. It expands structural Rust generation, improves type-driven lowering, and fixes a major compile-time performance regression exposed by Skia dogfood.
Added
- Add Rust type item generation from
@typealiases, including type aliases, structs, enums, and Rust-only support records that do not force generated Rustler term decoders. - Add
R.enum(...)for explicit Rust enum type items and fix zero-arity type alias resolution for bare@type name :: ...declarations. - Add structural
macro_rules!AST support and native validation/rendering for macro variables, captures, repetitions, and macro repeat expressions. - Add AST walking utilities for RustQ AST traversal and mutation analysis.
- Add golden corpus tooling for Rusty-Elixir lowering, with coverage reporting by category.
- Add semantic
enum_variant(Type, :variant, ...)lowering for constructing Rust enum variants from Rusty-Elixir without token macros. - Add semantic item-producing
defrustmacrocalls and macro item call support. - Add
defrustmacrosupport for compactskip_fieldsand sparse struct field descriptor row patterns. - Add idiomatic Clippy lint paths for Rust-facing attributes, such as
@allow Clippy.redundant_field_namesrendering to#[allow(clippy::redundant_field_names)]. - Add dev-only RustQ Reach smell checks and dogfood them in strict mode for raw
Rust escapes, low-level Rusty-Elixir control flow, trivial
defrustwrappers, and blocklessdefrustmodaliases.
Changed
- Extend
defrustmacrowith identifier/literal captures, item-generating innerdefrustbodies, multiple generated item bodies, andrepeat ... domacro-template repetitions. - Lower calls to known
defrustmacrohelpers as generated Rust macro invocations, and support remote Rust macro calls such asDebug.trace!(value). - Replace ad hoc macro metavariable spacing cleanup with structural macro repeat expression rendering.
- Remove the process dictionary from Rusty-Elixir lowering; lowering state is now threaded explicitly through expression, call-argument, alias, array, control-flow, return, and expected-expression lowering.
- Make context-aware expression lowering the primary lowering path.
- Expand type-driven checking for expected expression positions, including
returns, call arguments, struct fields,
casearms,ifbranches,with/for reducebodies, closures, field access, statics, array literals, and optionsome(...)wrappers. - Infer downstream types through value uses such as comparisons,
binary_search_by_key, receiver method calls, mutableVec::push, option case bindings, option adapters, package free functions, andSelf-based impl callables. - Import free functions from configured Cargo packages in addition to impl methods.
- Resolve
Selftypes in impl callable metadata to the concrete impl target. - Keep
__rustq_callables__/0exports limited to local RustQ callables instead of embedding all external/package callables into every module. External callables are still available during compilation through cached source/package resolution. - Document item-generating
defrustmacropatterns for compact generated Rust that keeps implementation logic in Rusty-Elixir. - Document when to use ordinary Elixir
defmacrofor authoring-layer reuse versusdefrustmacrofor intentionally reducing generated Rust size.
Fixed
- Parse Rust bare function pointer types structurally through
synmetadata instead of regexing rendered Rust type strings. - Mark function pointer types as non-decodable where Rustler decoding cannot be derived safely.
- Auto-borrow binary-search keys, array literals expected as slices, call
arguments, option
some(...)inners, option adapter inners, external statics, and several downstream value-use patterns. - Avoid applying
?toOption<T>values inResult/NifResultcontexts where Rust does not support that propagation boundary. - Infer external static types and generated static types for auto-borrowing.
- Preserve explicit
R.raw(...)as an escape hatch while using structuredsynmetadata where available. - Fix zero-cost package metadata reuse after Skia dogfood: generated checks that previously ballooned to roughly 75–80 seconds now avoid duplicating thousands of external callables into every module.
v0.8.3 - 2026-06-28
- Infer propagation for source-backed receiver method calls when the receiver type is known.
- Index Rust source callables by normalized receiver target names.
- Strengthen metadata guidance for avoiding
unwrap!when Rust source metadata can be configured.
v0.8.2 - 2026-06-26
- Add the RustQ agent skill to the Hex package and documentation.
- Add guidance for authoring RustQ generators with
defrust, AST builders, metadata, inference, and explicit escape boundaries. - Document expression-oriented Rusty-Elixir style and recursion/reducer patterns.
v0.8.1 - 2026-06-25
- Add Rustler fixed struct term helpers for cached keys, default values, and raw
NIF_TERMmap construction. - Use
Atom::from_strfor generated cached atom helpers. - Add missing Rust integer marker types to
RustQ.Type.
v0.8.0 - 2026-06-25
- Add semantic control-flow lowering for expression-oriented Rusty-Elixir.
- Support macro-generated
caseclauses indefrustlowering.
v0.7.0 - 2026-06-25
- Add
ok_or!(option_expr, error_expr)as the Rusty-Elixir idiom for explicitOption<T>toResult<T, E>propagation boundaries. - Extend type-driven propagation inference with nested Rust module function
metadata, downstream receiver-method let inference,
as_slice()adapters, source-backedFrom<A> for Bcompatibility forimpl Into<B>, and associated-type metadata such asimpl IntoIterator<Item = T>. - Add source-backed callable metadata from Rust source files, Cargo packages, and callable modules.
- Dogfood native RustQ AST generation in Rustler atom dispatch, tagged enum, macro item, and native codegen helper paths.
- Add structured diagnostics for RustQ configuration, source loading, lowering, and native rendering failures.
v0.6.0 - 2026-06-19
- Reframe Rusty Elixir as the high-level
defrustauthoring surface. - Add external Rust type specs through ordinary remote types such as
GeneratedOpts.OvalOpts.t(RustQ.Type.lifetime(:a)). - Add ordinary Elixir macro expansion before
defrustlowering so reusable Rusty-Elixir body fragments can usedefmacro,quote, andunquote. - Lower plural alias calls such as
Atoms.fill()to snake-case Rust module calls such asatoms::fill(). - Demote
RustQ.Meta.quotedandRustQ.Type.pathto low-level escape hatches instead of the normal authoring style. - Add small AST reuse bridges for RustQ-owned codegen helpers:
RustQ.Rust.ast_item/1,RustQ.Rust.ast_items/1, and the internalRustQ.Meta.item(module, name)/items(module, names)/ast!(module, name)helpers. - Add Rust AST support for receiver arguments and lifetime-bearing impl blocks,
including Rustler shapes such as
impl<'a> rustler::Decoder<'a> for Typeandfn encode<'a>(&self, env: rustler::Env<'a>) -> rustler::Term<'a>. - Dogfood Rustler helper generation through
defrustand RustQ AST builders: term helpers, opts helpers, term builders, cached atoms, atom decoders, tagged enum decoders/encoders, resources, and term decoder shells no longer rely on hand-written helper templates. Explicit rawNIF_TERMbuilders remain the unsafe escape hatch.
v0.5.1 - 2026-06-15
- Add generic Rust expression builders:
Rust.call_expr/3Rust.some/1Rust.none/0Rust.tuple/1Rust.cast/2Rust.question/1Rust.ref_expr/2
v0.5.0 - 2026-06-15
- Add template includes.
- Add composable RustQ splice groups.
- Use plain keyword splices for composition.
- Add structured include diagnostics.
- Add rustfmt option.
- Add generic Rust body/control-flow builders:
Rust.block/1Rust.let_/2Rust.let_mut/2Rust.assign/2Rust.call_stmt/3Rust.return_if/2Rust.if_/3Rust.if_let/4Rust.match_/2
- Document macro token placeholder limitation.
v0.4.0 - 2026-06-06
- Add generic Rustler builders for atom decoders, atom dispatch functions, and keyword/options helper functions.
v0.3.0 - 2026-06-06
- Remove the generic Rustler schema field group DSL. Prefer explicit fields in RustQ schemas or project-specific macros for domain shorthand.
v0.2.2 - 2026-06-03
- Add
RustQ.Rustler.resource_handle/2for generating a Rustler resource plus a decoder for Elixir-facing resource handle structs/maps. - Add Rustler schema field groups for reusable field sets with
use_fields/1. - Let Rustler schema nodes override generated Rust type names and Elixir module
names with
rust:andmodule:.
v0.2.1 - 2026-06-03
- Add
RustQ.Rustler.nif_export/2andnif_exports/1for generating exported Rustler NIF functions that delegate to handwritten implementation functions.
v0.2.0 - 2026-06-02
- Replace the separate
__expr_,__type_, and__splice_placeholder prefixes with one visually distinct__rq_placeholder prefix. - Templates now use forms like
__rq_Name,__rq_value!(),__rq_fields: (),, and__rq_items!();.
v0.1.2 - 2026-06-02
- Let Rustler schema field types reference schema nodes and tagged enums by
schema name, so examples can use
Contentinstead of generated Rust names likeExContent.
v0.1.1 - 2026-06-02
- Keep the packaged NIF crate out of parent Cargo workspaces when RustQ is used inside workspace-based projects.
v0.1.0 - 2026-06-02
Initial release.
- Parse, validate, render, bind, and splice Rust templates from Elixir.
~Rsigil for inline Rust templates.- Rust fragment builders for functions, structs, enums, impls, fields, constants, uses, modules, and type aliases.
- Rustler helper generators for atoms, NIFs, resources, option decoders, term helpers, term decoders, NIF structs, tagged enums, cached atoms, safe term builders, and explicit raw
NIF_TERMbuilders. rustq.exsmanifest DSL plusmix rustq.genfor generated file syncing and stale checks.- Rustler schema DSL for generating Rust NIF structs and tagged enums from Elixir schema definitions.