Rustler users often expose Elixir-facing APIs made of plain maps with atom keys. Those keys should follow Elixir naming conventions, not Rust identifier constraints.
A common issue is that natural Elixir map keys include names that are Rust keywords or otherwise awkward Rust identifiers:
%{
type: :import,
start: 0,
end: 12,
async: true
}
In Rust, these fields usually need different names:
struct ImportInfo {
type_: rustler::Atom,
start: u32,
end_: u32,
async_: bool,
}
Without a way to rename the derived atom names, users have to either change the Elixir API shape or write manual Encoder / Decoder implementations.
Two small additions would cover this pattern well:
#[rustler(rename = "...")] support for derived field / variant atom names.
- A
term_map! macro for constructing Erlang maps without parallel key/value arrays.
#[rustler(rename = "...")]
With rename support, users could keep idiomatic Elixir keys while still using valid Rust identifiers:
#[derive(NifMap)]
struct ImportInfo {
#[rustler(rename = "type")]
type_: rustler::Atom,
start: u32,
#[rustler(rename = "end")]
end_: u32,
#[rustler(rename = "async")]
async_: bool,
}
This would encode/decode:
%{
type: :import,
start: 0,
end: 12,
async: true
}
This mirrors a familiar Rust derive convention from Serde:
#[serde(rename = "type")]
type_: String
Potential scope:
NifMap field names
NifTaggedEnum named variant fields
NifTaggedEnum variant tags
NifUnitEnum, NifStruct, and NifRecord for consistency
Starting with only NifMap fields would already solve the most common plain-map case.
term_map!
Rustler already exposes the necessary low-level API for map construction, but common map construction currently requires parallel key/value arrays:
rustler::Term::map_from_term_arrays(
env,
&[
atoms::code().encode(env),
atoms::css().encode(env),
atoms::errors().encode(env),
atoms::warnings().encode(env),
],
&[
code.encode(env),
css.encode(env),
errors.encode(env),
warnings.encode(env),
],
)
.unwrap()
For larger maps, the key and value for a field are separated, which makes additions and reordering more error-prone.
A macro keeps each key next to its value:
rustler::term_map!(env, {
atoms::code() => code,
atoms::css() => css,
atoms::errors() => errors,
atoms::warnings() => warnings,
})
Expected behavior:
rustler::term_map!(env, {
atoms::code() => "console.log(1)",
atoms::css() => Option::<String>::None,
atoms::errors() => Vec::<String>::new(),
})
returns the same term as:
rustler::Term::map_from_term_arrays(
env,
&[
atoms::code().encode(env),
atoms::css().encode(env),
atoms::errors().encode(env),
],
&[
"console.log(1)".encode(env),
Option::<String>::None.encode(env),
Vec::<String>::new().encode(env),
],
)
.unwrap()
Rustler users often expose Elixir-facing APIs made of plain maps with atom keys. Those keys should follow Elixir naming conventions, not Rust identifier constraints.
A common issue is that natural Elixir map keys include names that are Rust keywords or otherwise awkward Rust identifiers:
In Rust, these fields usually need different names:
Without a way to rename the derived atom names, users have to either change the Elixir API shape or write manual
Encoder/Decoderimplementations.Two small additions would cover this pattern well:
#[rustler(rename = "...")]support for derived field / variant atom names.term_map!macro for constructing Erlang maps without parallel key/value arrays.#[rustler(rename = "...")]With rename support, users could keep idiomatic Elixir keys while still using valid Rust identifiers:
This would encode/decode:
This mirrors a familiar Rust derive convention from Serde:
Potential scope:
NifMapfield namesNifTaggedEnumnamed variant fieldsNifTaggedEnumvariant tagsNifUnitEnum,NifStruct, andNifRecordfor consistencyStarting with only
NifMapfields would already solve the most common plain-map case.term_map!Rustler already exposes the necessary low-level API for map construction, but common map construction currently requires parallel key/value arrays:
For larger maps, the key and value for a field are separated, which makes additions and reordering more error-prone.
A macro keeps each key next to its value:
Expected behavior:
returns the same term as: