Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve documentation of derive macros #429

Merged
merged 1 commit into from
Apr 12, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 112 additions & 41 deletions rustler_codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ enum RustlerAttr {
Tag(String),
}

/// Implementation of a Native Implemented Function (NIF) macro that lets the user annotate
/// a function that will be wrapped in higer-level NIF implementation.
/// Initialise the Native Implemented Function (NIF) environment
/// and register NIF functions in an Elixir module.
///
/// ```ignore
/// #[rustler::nif]
Expand Down Expand Up @@ -53,8 +53,9 @@ pub fn init(input: TokenStream) -> TokenStream {
output.into()
}

/// Implementation of a Native Implemented Function (NIF) macro that lets the user annotate
/// a function that will be wrapped in higer-level NIF implementation.
/// Wrap a function in a Native Implemented Function (NIF) implementation,
/// so that it can be called from Elixir,
/// with all encoding and decoding steps done automatically.
///
/// ```ignore
/// #[nif]
Expand All @@ -70,9 +71,10 @@ pub fn nif(args: TokenStream, input: TokenStream) -> TokenStream {
nif::transcoder_decorator(args, input).into()
}

/// Implementation of the `NifStruct` macro that lets the user annotate a struct that will
/// be translated directly from an Elixir struct to a Rust struct. For example, the following
/// struct, annotated as such:
/// Derives implementations for the `Encoder` and `Decoder` traits
/// which convert between an Elixir struct and a Rust struct.
///
/// For example, annotate the following Rust struct:
///
/// ```ignore
/// #[derive(Debug, NifStruct)]
Expand All @@ -83,22 +85,27 @@ pub fn nif(args: TokenStream, input: TokenStream) -> TokenStream {
/// }
/// ```
///
/// This would be translated by Rustler into:
/// Write the following corresponding Elixir struct definition:
///
/// ```elixir
/// defmodule AddStruct do
/// defstruct lhs: 0, rhs: 0
/// end
/// ```
///
/// Then the traits `Encoder` and `Decoder` are derived automatically for your Rust struct
/// such that you can use the Elixir struct definition for it.

#[proc_macro_derive(NifStruct, attributes(module, rustler))]
pub fn nif_struct(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
ex_struct::transcoder_decorator(&ast, false).into()
}

/// Implementation of the `NifException` macro that lets the user annotate a struct that will
/// be translated directly from an Elixir exception to a Rust struct. For example, the following
/// struct, annotated as such:
/// Derives implementations for the `Encoder` and `Decoder` traits
/// which convert between an Elixir exception and a Rust struct.
///
/// For example, annotate the following struct:
///
/// ```ignore
/// #[derive(Debug, NifException)]
Expand All @@ -108,22 +115,26 @@ pub fn nif_struct(input: TokenStream) -> TokenStream {
/// }
/// ```
///
/// This would be translated by Rustler into:
/// Write the corresponding Elixir exception definition:
///
/// ```elixir
/// defmodule AddException do
/// defexception message: ""
/// end
/// ```
///
/// Then the traits `Encoder` and `Decoder` are derived automatically for your Rust struct
/// such that you can use the Elixir exception definition for it.
#[proc_macro_derive(NifException, attributes(module, rustler))]
pub fn nif_exception(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
ex_struct::transcoder_decorator(&ast, true).into()
}

/// Implementation of a macro that lets the user annotate a struct with `NifMap` so that the
/// struct can be encoded or decoded from an Elixir map. For example, the following struct
/// annotated as such:
/// Derives implementations for the `Encoder` and `Decoder` traits
/// which convert between Rust struct and an Elixir map.
///
/// For example, annotate the following struct:
///
/// ```ignore
/// #[derive(NifMap)]
Expand All @@ -133,21 +144,31 @@ pub fn nif_exception(input: TokenStream) -> TokenStream {
/// }
/// ```
///
/// Given the values 33 and 21 for this struct, this would result, when encoded, in an elixir
/// Create a value of that type:
///
/// ```ignore
/// let value = AddMap { lhs: 33, rhs: 21 };
/// ```
///
/// Then the traits `Encoder` and `Decoder` are derived automatically for your Rust struct
/// such that encoding `value` would result in an elixir
/// map with two elements like:
///
/// ```elixir
/// %{lhs: 33, rhs: 21}
/// ```
///
/// And vice versa, decoding this map would result in `value`.
#[proc_macro_derive(NifMap, attributes(rustler))]
pub fn nif_map(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
map::transcoder_decorator(&ast).into()
}

/// Implementation of a macro that lets the user annotate a struct with `NifTuple` so that the
/// struct can be encoded or decoded from an Elixir tuple. For example, the following struct
/// annotated as such:
/// Derives implementations for the `Encoder` and `Decoder` traits
/// which convert between a Rust struct and an Elixir tuple.
///
/// For example, annotate the following struct:
///
/// ```ignore
/// #[derive(NifTuple)]
Expand All @@ -157,23 +178,33 @@ pub fn nif_map(input: TokenStream) -> TokenStream {
/// }
/// ```
///
/// Given the values 33 and 21 for this struct, this would result, when encoded, in an elixir
/// Create a value of that type:
///
/// ```ignore
/// let value = AddMap { lhs: 33, rhs: 21 };
/// ```
///
/// Then the traits `Encoder` and `Decoder` are derived automatically for your Rust struct
/// such that encoding `value` would result in an elixir
/// tuple with two elements like:
///
/// ```elixir
/// {33, 21}
/// ```
///
/// And vice versa, decoding this map would result in `value`.
///
/// The size of the tuple will depend on the number of elements in the struct.
#[proc_macro_derive(NifTuple, attributes(rustler))]
pub fn nif_tuple(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
tuple::transcoder_decorator(&ast).into()
}

/// Implementation of the `NifRecord` macro that lets the user annotate a struct that will
/// be translated directly from an Elixir struct to a Rust struct. For example, the following
/// struct, annotated as such:
/// Derives implementations for the `Encoder` and `Decoder` traits
/// which convert between a Rust struct and an Elixir record.
///
/// For example, annotate the following struct:
///
/// ```ignore
/// #[derive(Debug, NifRecord)]
Expand All @@ -184,22 +215,41 @@ pub fn nif_tuple(input: TokenStream) -> TokenStream {
/// }
/// ```
///
/// This would be translated by Rustler into:
/// Create a value of that type:
///
/// ```ignore
/// let value = AddRecord { lhs: 33, rhs: 21 };
/// ```
///
/// Then the traits `Encoder` and `Decoder` are derived automatically for your Rust struct
/// such that `value` would be encoded into the following elixir value:
///
/// ```elixir
/// {:record, 33, 21}
/// ```
///
/// If you supply the following matching Elixir record definition:
///
/// ```elixir
/// defmodule AddRecord do
/// import Record
/// defrecord :record, [lhs: 1, rhs: 2]
/// end
/// ```
///
/// Then you can use record functions such as `AddRecord.record/0`, `AddRecord.record/1`, `AddRecord.record/2`,
/// to work with the encoded data,
/// and to create data that can be decoded back into your Rust struct.
#[proc_macro_derive(NifRecord, attributes(tag, rustler))]
pub fn nif_record(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
record::transcoder_decorator(&ast).into()
}

/// Implementation of the `NifUnitEnum` macro that lets the user annotate an enum with a unit type
/// that will generate elixir atoms when encoded
/// Derives implementations for the `Encoder` and `Decoder` traits
/// which convert between an enum and a union of elixir atoms.
///
/// For example:
///
/// ```ignore
/// #[derive(NifUnitEnum)]
Expand All @@ -209,13 +259,27 @@ pub fn nif_record(input: TokenStream) -> TokenStream {
/// }
/// ```
///
/// An example usage in elixir would look like the following.
/// Then the traits `Encoder` and `Decoder` are derived automatically for your Rust struct
/// such that `FooBar` is encoded to, and decoded from, `:foo_bar`.
/// - The variant name is translated from camel case to snake case for the atom name.
/// - Each constructor is required not to have arguments, i.e. to be of unit type.
///
/// An example usage in Rust and Elixir would look like the following.
///
/// ```ignore
/// #[rustler::nif]
/// pub fn unit_enum_echo(unit_enum: UnitEnum) -> UnitEnum {
/// unit_enum
/// }
/// ```
///
/// (We are leaving out some boiler plate code to connect the rust code to elixir functions.)
///
/// ```elixir
/// test "unit enum transcoder" do
/// assert :foo_bar == RustlerTest.unit_enum_echo(:foo_bar)
/// assert :baz == RustlerTest.unit_enum_echo(:baz)
/// assert :invalid_variant == RustlerTest.unit_enum_echo(:somethingelse)
/// assert :foo_bar == unit_enum_echo(:foo_bar)
/// assert :baz == unit_enum_echo(:baz)
/// assert :invalid_variant == unit_enum_echo(:somethingelse)
/// end
/// ```
///
Expand All @@ -227,9 +291,15 @@ pub fn nif_unit_enum(input: TokenStream) -> TokenStream {
unit_enum::transcoder_decorator(&ast).into()
}

/// Implementation of the `NifUntaggedEnum` macro that lets the user annotate an enum that will
/// generate elixir values when decoded. This can be used for rust enums that contain data and
/// will generate a value based on the kind of data encoded. For example from the test code:
/// Derives implementations for the `Encoder` and `Decoder` traits
/// which convert between a Rust enum and a union of Elixir types.
///
/// This can be used for Rust enums that contain several constructors containing different types of data,
/// each implementing the `Encoder` and `Decoder` traits.
/// An enum value will be encoded based on the constructor used,
/// and an Elixir value will be decoded based on the value.
///
/// For example from the test code:
///
/// ```ignore
/// #[derive(NifUntaggedEnum)]
Expand All @@ -239,20 +309,21 @@ pub fn nif_unit_enum(input: TokenStream) -> TokenStream {
/// Baz(AddStruct),
/// }
///
/// pub fn untagged_enum_echo<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult<Term<'a>> {
/// let untagged_enum: UntaggedEnum = args[0].decode()?;
/// Ok(untagged_enum.encode(env))
/// #[rustler::nif]
/// pub fn untagged_enum_echo(untagged_enum: UntaggedEnum) -> UntaggedEnum {
/// untagged_enum
/// }
/// ```
///
/// This can be used from elixir in the following manner.
/// Adding boiler plate code to connect Rust code to elixir functions,
/// this can be used from elixir in the following manner.
///
/// ```elixir
/// test "untagged enum transcoder" do
/// assert 123 == RustlerTest.untagged_enum_echo(123)
/// assert "Hello" == RustlerTest.untagged_enum_echo("Hello")
/// assert %AddStruct{lhs: 45, rhs: 123} = RustlerTest.untagged_enum_echo(%AddStruct{lhs: 45, rhs: 123})
/// assert :invalid_variant == RustlerTest.untagged_enum_echo([1,2,3,4])
/// assert 123 == untagged_enum_echo(123)
/// assert "Hello" == untagged_enum_echo("Hello")
/// assert %AddStruct{lhs: 45, rhs: 123} = untagged_enum_echo(%AddStruct{lhs: 45, rhs: 123})
/// assert :invalid_variant == untagged_enum_echo([1,2,3,4])
/// end
/// ```
///
Expand Down