serde_rustler

Crates.io Documentation MIT license

serde_rustler provides a Serde Serializer and Deserializer for Rustler types, so you can easily serialize and deserialize native Rust types directly to and from native Elixir terms within your NIFs.

Installation

Install from Crates.io:

toml [dependencies] serde_rustler = "0.0.2"

API Overview

Below is an example of how you might use serde_rustler within a rust NIF:

```rust

[macro_use]

extern crate rustler;

use rustler::{Env, error::Error as NifError, NifResult, Term}; use serde::{Serialize, Deserialize}; use serderustler::{fromterm, to_term};

rustlerexportnifs! { "Elixir.SerdeNif", [("readme", 1, readme)], None }

// NOTE: to serialize to the correct Elixir record, you MUST tell serde to // rename the variants to the full Elixir record module atom.

[derive(Debug, Serialize, Deserialize)]

enum AnimalType { #[serde(rename = "Elixir.SerdeNif.AnimalType.Cat")] Cat(String), #[serde(rename = "Elixir.SerdeNif.AnimalType.Dog")] Dog(String), }

// NOTE: to serialize to an actual Elixir struct (rather than a just map with // a :struct key), you MUST tell serde to rename the struct to the full // Elixir struct module atom.

[derive(Debug, Serialize, Deserialize)]

[serde(rename = "Elixir.SerdeNif.Animal")]

struct Animal { #[serde(rename = "type")] _type: AnimalType, name: String, age: u8, owner: Option, }

fn readme<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult> { let animal: Animal = from_term(args[0]).or(Err(NifError::BadArg))?;

println!("serialized animal: {:?}", animal);

to_term(env, animal).or(Err(NifError::BadArg))

} ```

Corresponding Elixir code (code structure, imports, aliases and requires simplified or omitted for brevity):

```elixir defmodule SerdeNif do use Rustler, otpapp: :serdenif

def readme(term), do: :erlang.niferror(:nifnotloaded)

defmodule Animal do @type t :: %Animal{ type: Cat.t() | Dog.t(), name: bitstring, age: pos_integer, owner: nil | bitstring } defstruct type: Cat.record(), name: "", age: 0, owner: nil

@doc "Deserializes term as a Rust `Animal` struct, then serializes it back into an Elixir `Animal` struct. Should return true."
def test() do
  animal = %Animal{
    type: Animal.Cat.record(),
    name: "Garfield",
    age: 41,
  }

  SerdeNif.readme(animal) == animal
end

end

defmodule AnimalType.Cat do require Record @type t {MODULE, String.t()} Record.defrecord(:record, MODULE, breed: "tabby") end

defmodule AnimalType.Dog do require Record @type t {MODULE, String.t()} Record.defrecord(:record, :Dog, breed: "mutt") end end ```

Conversion Table

| Type Name | Serde (Rust) Values | Elixir Terms (default behaviour) | |-----------|------------------|---------------------| | bool | true or false | true or false | | 1 number | i8, i16, i32, i64, u8, u16, u32, u64, f32, f64 (TODO: i128 and u128) | number | | 1 char | "" | bitstring | | string | "" | bitstring | | 2 byte array | &[u8] or Vec<u8> | <<_::_*8>> | | option | Some(T) or None | T or :nil | | unit | None | :nil | | unit struct | struct Unit | :nil | | 3 unit variant | E::A in enum UnitVariant { A } | :A | | 3 newtype struct | struct Millimeters(u8) | {:Millimeters, u8} | | 3 newtype variant | E::N in enum E { N(u8) } | {:N, u8} | | newtype variant (any Ok and Err tagged enum) | enum R<T, E> { Ok(T), Err(E) } | {:ok, T} or {:error, E} | | seq | Vec<T> | [T] | | tuple | (u8,) | {u8,} | | 3 tuple struct | struct Rgb(u8, u8, u8) | {:Rgb, u8, u8, u8} | | 3 tuple variant | E::T in enum E { T(u8, u8) } | {:T, u8, u8} | | 1 map | HashMap<K, V> | %{} | | 3 struct | struct Rgb { r: u8, g: u8, b: u8 } | %Rgb{ r: byte, g: byte, b: byte } | | 3 struct variant | E::S in enum E { Rgb { r: u8, g: u8, b: u8 } } | %Rgb{ r: byte, g: byte, b: byte } |

1: API still being decided / implemented.

2: Requires specifying a specific serialize implementation, such as serde_bytes.

3: When serializing unknown input to terms, atoms will not be created and will instead be replaced with Elixir bitstrings. Therefore "records" will be tuples ({bitstring, ...}) and "structs" will be maps containing %{:__struct__ => bitstring}. The unfortunate consequence of this is that deserialize_any will lack the necessary information needed deserialize many terms without type hints, such as structs, enums and enum variants, and tuples. (Feedback on how best to solve this is very welcome here).

TODO

Changelog

| Version | Change Summary | | ------- | ---------------| | v0.0.2 | cleanup, better deserialize_any support | | v0.0.1 | initial release |

Contributing

  1. Fork it https://github.com/yourusername/serderustler/fork
  2. Create your feature branch (git checkout -b feature/fooBar)
  3. Commit your changes (git commit -am 'Add some fooBar')
  4. Push to the branch (git push origin feature/fooBar)
  5. Create a new Pull Request

Maintainers

License

MIT