rustler_codegen/
lib.rs

1#![recursion_limit = "128"]
2
3use proc_macro::TokenStream;
4
5mod context;
6mod encode_decode_templates;
7mod ex_struct;
8mod init;
9mod map;
10mod nif;
11mod record;
12mod resource_impl;
13mod tagged_enum;
14mod tuple;
15mod unit_enum;
16mod untagged_enum;
17
18#[derive(Debug)]
19enum RustlerAttr {
20    Encode,
21    Decode,
22    Module(String),
23    Tag(String),
24}
25
26/// Initialise the Native Implemented Function (NIF) environment
27/// and register NIF functions in an Elixir module.
28///
29/// ```ignore
30/// #[rustler::nif]
31/// fn add(a: i64, b: i64) -> i64 {
32///     a + b
33/// }
34///
35/// #[rustler::nif]
36/// fn sub(a: i64, b: i64) -> i64 {
37///     a - b
38/// }
39///
40/// #[rustler::nif]
41/// fn mul(a: i64, b: i64) -> i64 {
42///     a * b
43/// }
44///
45/// #[rustler::nif]
46/// fn div(a: i64, b: i64) -> i64 {
47///     a / b
48/// }
49///
50/// fn load(env: Env, _term: Term) -> bool {
51///     true
52/// }
53///
54/// rustler::init!("Elixir.Math", load = load);
55/// ```
56#[proc_macro]
57pub fn init(input: TokenStream) -> TokenStream {
58    let input = syn::parse_macro_input!(input as init::InitMacroInput);
59    let output: proc_macro2::TokenStream = input.into();
60    output.into()
61}
62
63/// Wrap a function in a Native Implemented Function (NIF) implementation,
64/// so that it can be called from Elixir,
65/// with all encoding and decoding steps done automatically.
66///
67/// ```ignore
68/// #[nif]
69/// fn add(a: i64, b: i64) -> i64 {
70///     a + b
71/// }
72/// ```
73///
74/// For functions that may take some time to return - let's say more than 1 millisecond - it is
75/// recommended to use the `schedule` flag. This tells the BEAM to allocate that NIF call
76/// to a special scheduler. These special schedulers are called "dirty" schedulers.
77///
78/// We can have two types of "lengthy work" functions: those that are CPU intensive
79/// and those that are IO intensive. They should be flagged with "DirtyCpu" and "DirtyIo",
80/// respectively.
81///
82/// See: <https://www.erlang.org/doc/man/erl_nif.html#lengthy_work>
83///
84/// ```ignore
85/// #[nif(schedule = "DirtyCpu")]
86/// pub fn my_lengthy_work() -> i64 {
87///     let duration = Duration::from_millis(100);
88///     std::thread::sleep(duration);
89///
90///     42
91/// }
92/// ```
93#[proc_macro_attribute]
94pub fn nif(args: TokenStream, input: TokenStream) -> TokenStream {
95    let mut nif_attributes = nif::NifAttributes::default();
96
97    if !args.is_empty() {
98        let nif_macro_parser = syn::meta::parser(|meta| nif_attributes.parse(meta));
99
100        syn::parse_macro_input!(args with nif_macro_parser);
101    }
102
103    let input = syn::parse_macro_input!(input as syn::ItemFn);
104
105    nif::transcoder_decorator(nif_attributes, input).into()
106}
107
108/// Derives implementations for the `Encoder` and `Decoder` traits
109/// which convert between an Elixir struct and a Rust struct.
110///
111/// For example, annotate the following Rust struct:
112///
113/// ```ignore
114/// #[derive(Debug, NifStruct)]
115/// #[module = "AddStruct"]
116/// struct AddStruct {
117///    lhs: i32,
118///    rhs: i32,
119/// }
120/// ```
121///
122/// Write the following corresponding Elixir struct definition:
123///
124/// ```elixir
125/// defmodule AddStruct do
126///   defstruct lhs: 0, rhs: 0
127/// end
128/// ```
129///
130/// Then the traits `Encoder` and `Decoder` are derived automatically for your Rust struct
131/// such that you can use the Elixir struct definition for it.
132#[proc_macro_derive(NifStruct, attributes(module, rustler))]
133pub fn nif_struct(input: TokenStream) -> TokenStream {
134    let ast = syn::parse(input).unwrap();
135    ex_struct::transcoder_decorator(&ast, false).into()
136}
137
138/// Derives implementations for the `Encoder` and `Decoder` traits
139/// which convert between an Elixir exception and a Rust struct.
140///
141/// For example, annotate the following struct:
142///
143/// ```ignore
144/// #[derive(Debug, NifException)]
145/// #[module = "AddException"]
146/// pub struct AddException {
147///     message: String,
148/// }
149/// ```
150///
151/// Write the corresponding Elixir exception definition:
152///
153/// ```elixir
154/// defmodule AddException do
155///   defexception message: ""
156/// end
157/// ```
158///
159/// Then the traits `Encoder` and `Decoder` are derived automatically for your Rust struct
160/// such that you can use the Elixir exception definition for it.
161#[proc_macro_derive(NifException, attributes(module, rustler))]
162pub fn nif_exception(input: TokenStream) -> TokenStream {
163    let ast = syn::parse(input).unwrap();
164    ex_struct::transcoder_decorator(&ast, true).into()
165}
166
167/// Derives implementations for the `Encoder` and `Decoder` traits
168/// which convert between Rust struct and an Elixir map.
169///
170/// For example, annotate the following struct:
171///
172/// ```ignore
173/// #[derive(NifMap)]
174/// struct AddMap {
175///     lhs: i32,
176///     rhs: i32,
177/// }
178/// ```
179///
180/// Create a value of that type:
181///
182/// ```ignore
183/// let value = AddMap { lhs: 33, rhs: 21 };
184/// ```
185///
186/// Then the traits `Encoder` and `Decoder` are derived automatically for your Rust struct
187/// such that encoding `value` would result in an elixir
188/// map with two elements like:
189///
190/// ```elixir
191/// %{lhs: 33, rhs: 21}
192/// ```
193///
194/// And vice versa, decoding this map would result in `value`.
195#[proc_macro_derive(NifMap, attributes(rustler))]
196pub fn nif_map(input: TokenStream) -> TokenStream {
197    let ast = syn::parse(input).unwrap();
198    map::transcoder_decorator(&ast).into()
199}
200
201/// Derives implementations for the `Encoder` and `Decoder` traits
202/// which convert between a Rust struct and an Elixir tuple.
203///
204/// For example, annotate the following struct:
205///
206/// ```ignore
207/// #[derive(NifTuple)]
208/// struct AddTuple {
209///     lhs: i32,
210///     rhs: i32,
211/// }
212/// ```
213///
214/// Create a value of that type:
215///
216/// ```ignore
217/// let value = AddMap { lhs: 33, rhs: 21 };
218/// ```
219///
220/// Then the traits `Encoder` and `Decoder` are derived automatically for your Rust struct
221/// such that encoding `value` would result in an elixir
222/// tuple with two elements like:
223///
224/// ```elixir
225/// {33, 21}
226/// ```
227///
228/// And vice versa, decoding this map would result in `value`.
229///
230/// The size of the tuple will depend on the number of elements in the struct.
231#[proc_macro_derive(NifTuple, attributes(rustler))]
232pub fn nif_tuple(input: TokenStream) -> TokenStream {
233    let ast = syn::parse(input).unwrap();
234    tuple::transcoder_decorator(&ast).into()
235}
236
237/// Derives implementations for the `Encoder` and `Decoder` traits
238/// which convert between a Rust struct and an Elixir record.
239///
240/// For example, annotate the following struct:
241///
242/// ```ignore
243/// #[derive(Debug, NifRecord)]
244/// #[tag = "record"]
245/// struct AddRecord {
246///    lhs: i32,
247///    rhs: i32,
248/// }
249/// ```
250///
251/// Create a value of that type:
252///
253/// ```ignore
254/// let value = AddRecord { lhs: 33, rhs: 21 };
255/// ```
256///
257/// Then the traits `Encoder` and `Decoder` are derived automatically for your Rust struct
258/// such that `value` would be encoded into the following elixir value:
259///
260/// ```elixir
261/// {:record, 33, 21}
262/// ```
263///
264/// If you supply the following matching Elixir record definition:
265///
266/// ```elixir
267/// defmodule AddRecord do
268///   import Record
269///   defrecord :record, [lhs: 1, rhs: 2]
270/// end
271/// ```
272///
273/// Then you can use record functions such as `AddRecord.record/0`, `AddRecord.record/1`, `AddRecord.record/2`,
274/// to work with the encoded data,
275/// and to create data that can be decoded back into your Rust struct.
276#[proc_macro_derive(NifRecord, attributes(tag, rustler))]
277pub fn nif_record(input: TokenStream) -> TokenStream {
278    let ast = syn::parse(input).unwrap();
279    record::transcoder_decorator(&ast).into()
280}
281
282/// Derives implementations for the `Encoder` and `Decoder` traits
283/// which convert between an enum and a union of elixir atoms.
284///
285/// For example:
286///
287/// ```ignore
288/// #[derive(NifUnitEnum)]
289/// enum UnitEnum {
290///    FooBar,
291///    Baz,
292/// }
293/// ```
294///
295/// Then the traits `Encoder` and `Decoder` are derived automatically for your Rust struct
296/// such that `FooBar` is encoded to, and decoded from, `:foo_bar`.
297/// - The variant name is translated from camel case to snake case for the atom name.
298/// - Each constructor is required not to have arguments, i.e. to be of unit type.
299///
300/// An example usage in Rust and Elixir would look like the following.
301///
302/// ```ignore
303/// #[rustler::nif]
304/// pub fn unit_enum_echo(unit_enum: UnitEnum) -> UnitEnum {
305///     unit_enum
306/// }
307/// ```
308///
309/// (We are leaving out some boiler plate code to connect the rust code to elixir functions.)
310///
311/// ```elixir
312/// test "unit enum transcoder" do
313///   assert :foo_bar == unit_enum_echo(:foo_bar)
314///   assert :baz == unit_enum_echo(:baz)
315///   assert :invalid_variant == unit_enum_echo(:somethingelse)
316/// end
317/// ```
318///
319/// Note that the `:invalid_variant` atom is returned if the user tries to encode something
320/// that isn't in the Rust enum.
321#[proc_macro_derive(NifUnitEnum, attributes(rustler))]
322pub fn nif_unit_enum(input: TokenStream) -> TokenStream {
323    let ast = syn::parse(input).unwrap();
324    unit_enum::transcoder_decorator(&ast).into()
325}
326
327/// Implementation of the `NifTaggedEnum` macro that lets the user annotate an enum that will
328/// generate elixir values when encoded. This can be used for any rust enums and will generate
329/// three types of values based on the kind of the enum. For example from the test code:
330///
331/// ```ignore
332/// #[derive(NifTaggedEnum)]
333/// enum TaggedEnum {
334///     Foo,
335///     Bar(String),
336///     Baz{ a: i32, b: i32 },
337/// }
338///
339/// pub fn tagged_enum_echo<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult<Term<'a>> {
340///     let tagged_enum: TaggedEnum = args[0].decode()?;
341///     Ok(tagged_enum.encode(env))
342/// }
343/// ```
344///
345/// This can be used from elixir in the following manner.
346///
347/// ```elixir
348/// test "tagged enum transcoder" do
349///   assert :foo == RustlerTest.tagged_enum_echo(:foo)
350///   assert {:bar, "Hello"} == RustlerTest.tagged_enum_echo(:bar, "Hello")
351///   assert {:baz, %{a: 33, b: 21}} == RustlerTest.tagged_enum_echo({:baz, %{a: 33, b: 21}})
352/// end
353/// ```
354#[proc_macro_derive(NifTaggedEnum, attributes(rustler))]
355pub fn nif_tagged_enum(input: TokenStream) -> TokenStream {
356    let ast = syn::parse(input).unwrap();
357    tagged_enum::transcoder_decorator(&ast).into()
358}
359
360/// Derives implementations for the `Encoder` and `Decoder` traits
361/// which convert between a Rust enum and a union of Elixir types.
362///
363/// This can be used for Rust enums that contain several constructors containing different types of data,
364/// each implementing the `Encoder` and `Decoder` traits.
365/// An enum value will be encoded based on the constructor used,
366/// and an Elixir value will be decoded based on the value.
367///
368/// For example from the test code:
369///
370/// ```ignore
371/// #[derive(NifUntaggedEnum)]
372/// enum UntaggedEnum {
373///     Foo(u32),
374///     Bar(String),
375///     Baz(AddStruct),
376/// }
377///
378/// #[rustler::nif]
379/// pub fn untagged_enum_echo(untagged_enum: UntaggedEnum) -> UntaggedEnum {
380///     untagged_enum
381/// }
382/// ```
383///
384/// Adding boiler plate code to connect Rust code to elixir functions,
385/// this can be used from elixir in the following manner.
386///
387/// ```elixir
388/// test "untagged enum transcoder" do
389///   assert 123 == untagged_enum_echo(123)
390///   assert "Hello" == untagged_enum_echo("Hello")
391///   assert %AddStruct{lhs: 45, rhs: 123} = untagged_enum_echo(%AddStruct{lhs: 45, rhs: 123})
392///   assert :invalid_variant == untagged_enum_echo([1,2,3,4])
393/// end
394/// ```
395///
396/// Note that the type of elixir return is dependent on the data in the enum and the actual enum
397/// type is lost in the translation because Elixir has no such concept.
398#[proc_macro_derive(NifUntaggedEnum, attributes(rustler))]
399pub fn nif_untagged_enum(input: TokenStream) -> TokenStream {
400    let ast = syn::parse(input).unwrap();
401    untagged_enum::transcoder_decorator(&ast).into()
402}
403
404/// Helper attribute for `Resource` implementations
405///
406/// When an `impl Resource for Type` block is annotated with this attribute, it will automatically
407/// set the `IMPLEMENTS_...` associated constants for all implemented callback methods. Thus,
408/// instead of
409///
410/// ```ignore
411/// struct ResourceType {}
412///
413/// impl Resource for ResourceType
414/// {
415///     const IMPLEMENTS_DESTRUCTOR: bool = true;
416///
417///     fn destructor(...) { ... }
418/// }
419/// ```
420/// it is enough to provide the implementation:
421/// ```ignore
422/// #[rustler::resource_impl]
423/// impl Resource for ResourceType
424/// {
425///     fn destructor(...) { ... }
426/// }
427/// ```
428///
429/// The resource type is also automatically registered by default, it does not have to be manually
430/// registered in a `load` callback. The automatic registration can be disabled with the `register`
431/// parameter:
432///
433/// ```ignore
434/// #[rustler::resource_impl]
435/// impl Resource for ResourceType
436/// {
437///     ...
438/// }
439///
440/// // no load callback necessary
441/// ```
442///
443/// If registration is disabled, the resource type has to be registered manually. It is not
444/// possible to use the old `resource!` macro for this, as that injects another `impl Resource`
445/// block.
446/// ```ignore
447/// #[rustler::resource_impl(register = false)]
448/// impl Resource for ResourceType
449/// {
450///     ...
451/// }
452///
453/// pub fn on_load(env: Env) -> bool {
454///     env.register::<ResourceType>().is_ok()
455/// }
456/// ```
457#[proc_macro_attribute]
458pub fn resource_impl(args: TokenStream, item: TokenStream) -> TokenStream {
459    let mut attributes = resource_impl::Attributes::default();
460
461    if !args.is_empty() {
462        let parser = syn::meta::parser(|meta| attributes.parse(meta));
463
464        syn::parse_macro_input!(args with parser);
465    }
466    let input = syn::parse_macro_input!(item as syn::ItemImpl);
467
468    resource_impl::transcoder_decorator(attributes, input).into()
469}