From 61a2ed8d52b3ae95d9b4e0b6a78585a9b395770f Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 24 Sep 2023 13:55:54 +0200 Subject: [PATCH 01/28] Move deprecation warning to correct branch (#564) --- rustler_mix/lib/rustler/compiler/config.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rustler_mix/lib/rustler/compiler/config.ex b/rustler_mix/lib/rustler/compiler/config.ex index badaac41..dc508fe0 100644 --- a/rustler_mix/lib/rustler/compiler/config.ex +++ b/rustler_mix/lib/rustler/compiler/config.ex @@ -38,12 +38,12 @@ defmodule Rustler.Compiler.Config do # TODO: Remove in 1.0 rustler_crates = if mix_config = Mix.Project.config()[:rustler_crates] do - mix_config - else IO.warn( ":rustler_crates in mix.exs is deprecated, please explicitly pass options on `use Rustler` or configure the module in your `config/*.exs` files" ) + mix_config + else [] end From 851d53f0a8e2b4119414296e0b4515dac0e8a749 Mon Sep 17 00:00:00 2001 From: bcksl <121328003+bcksl@users.noreply.github.com> Date: Tue, 26 Sep 2023 13:09:49 +0300 Subject: [PATCH 02/28] Make send return Results (#563) * Return Result from send, add a test and update docs Co-authored-by: Benedikt Reinartz --- CHANGELOG.md | 1 + UPGRADE.md | 10 +++++ rustler/src/env.rs | 37 +++++++++++++++---- rustler/src/thread.rs | 2 +- rustler_tests/lib/rustler_test.ex | 1 + rustler_tests/native/rustler_test/src/lib.rs | 1 + .../native/rustler_test/src/test_env.rs | 14 +++++-- rustler_tests/test/env_test.exs | 19 ++++++++++ 8 files changed, 73 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54a65d8a..2f5dec17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ See [`UPGRADE.md`](./UPGRADE.md) for additional help when upgrading to newer ver * Mark `use Rustler` module configuration as compile-time * Bump Rust edition to 2021 * Make `:rustler` a compile-time-only dependency (#516, #559) +* Return `Result<(), SendError>` from all `send` functions (#239, #563) ## [0.29.1] - 2023-06-30 diff --git a/UPGRADE.md b/UPGRADE.md index 52121e55..5beaaa05 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -8,6 +8,16 @@ This document is intended to simplify upgrading to newer versions by extending t options on `use Rustler` or configuring the module in your `config/*.exs` files. +Additionally, `Env::send` and `OwnedEnv::send_and_clear` will now return a +`Result`. Updating will thus introduce warnings about unused `Result`s. To +remove the warnings without changing behaviour, the `Result`s can be "used" as +```rust +let _ = env.send(...) +``` +Neither the `Ok` nor the `Err` case carry additional information so far. An +error is returned if either the receiving or the sending process is dead. See +also [enif\_send](https://www.erlang.org/doc/man/erl_nif.html#enif_send). + ## 0.28 -> 0.29 `RUSTLER_NIF_VERSION` is deprecated and will not be considered anymore for 0.30. diff --git a/rustler/src/env.rs b/rustler/src/env.rs index bd6af495..88cad3eb 100644 --- a/rustler/src/env.rs +++ b/rustler/src/env.rs @@ -30,6 +30,10 @@ impl<'a, 'b> PartialEq> for Env<'a> { } } +/// +#[derive(Clone, Copy, Debug)] +pub struct SendError; + impl<'a> Env<'a> { /// Create a new Env. For the `_lifetime_marker` argument, pass a /// reference to any local variable that has its own lifetime, different @@ -71,12 +75,15 @@ impl<'a> Env<'a> { /// /// * The current thread is *not* managed by the Erlang VM. /// + /// The result indicates whether the send was successful, see also + /// [enif\_send](https://www.erlang.org/doc/man/erl_nif.html#enif_send). + /// /// # Panics /// /// Panics if the above rules are broken (by trying to send a message from /// an `OwnedEnv` on a thread that's managed by the Erlang VM). /// - pub fn send(self, pid: &LocalPid, message: Term<'a>) { + pub fn send(self, pid: &LocalPid, message: Term<'a>) -> Result<(), SendError> { let thread_type = unsafe { rustler_sys::enif_thread_type() }; let env = if thread_type == rustler_sys::ERL_NIF_THR_UNDEFINED { ptr::null_mut() @@ -93,8 +100,14 @@ impl<'a> Env<'a> { }; // Send the message. - unsafe { - rustler_sys::enif_send(env, pid.as_c_arg(), ptr::null_mut(), message.as_c_arg()); + let res = unsafe { + rustler_sys::enif_send(env, pid.as_c_arg(), ptr::null_mut(), message.as_c_arg()) + }; + + if res == 1 { + Ok(()) + } else { + Err(SendError) } } @@ -164,7 +177,7 @@ impl<'a> Env<'a> { /// /// fn send_string_to_pid(data: &str, pid: &LocalPid) { /// let mut msg_env = OwnedEnv::new(); -/// msg_env.send_and_clear(pid, |env| data.encode(env)); +/// let _ = msg_env.send_and_clear(pid, |env| data.encode(env)); /// } /// /// There's no way to run Erlang code in an `OwnedEnv`. It's not a process. It's just a workspace @@ -198,13 +211,15 @@ impl OwnedEnv { /// The environment is cleared as though by calling the `.clear()` method. /// To avoid that, use `env.send(pid, term)` instead. /// + /// The result is the same as what `Env::send` would return. + /// /// # Panics /// /// Panics if called from a thread that is managed by the Erlang VM. You /// can only use this method on a thread that was created by other /// means. (This curious restriction is imposed by the Erlang VM.) /// - pub fn send_and_clear(&mut self, recipient: &LocalPid, closure: F) + pub fn send_and_clear(&mut self, recipient: &LocalPid, closure: F) -> Result<(), SendError> where F: for<'a> FnOnce(Env<'a>) -> Term<'a>, { @@ -214,11 +229,17 @@ impl OwnedEnv { let message = self.run(|env| closure(env).as_c_arg()); - unsafe { - rustler_sys::enif_send(ptr::null_mut(), recipient.as_c_arg(), *self.env, message); - } + let res = unsafe { + rustler_sys::enif_send(ptr::null_mut(), recipient.as_c_arg(), *self.env, message) + }; self.clear(); + + if res == 1 { + Ok(()) + } else { + Err(SendError) + } } /// Free all terms in this environment and clear it for reuse. diff --git a/rustler/src/thread.rs b/rustler/src/thread.rs index 763b0bb0..e189393a 100644 --- a/rustler/src/thread.rs +++ b/rustler/src/thread.rs @@ -41,7 +41,7 @@ where { let pid = env.pid(); S::spawn(move || { - OwnedEnv::new().send_and_clear(&pid, |env| { + let _ = OwnedEnv::new().send_and_clear(&pid, |env| { match panic::catch_unwind(|| thread_fn(env)) { Ok(term) => term, Err(err) => { diff --git a/rustler_tests/lib/rustler_test.ex b/rustler_tests/lib/rustler_test.ex index d75ebaa6..52950aa6 100644 --- a/rustler_tests/lib/rustler_test.ex +++ b/rustler_tests/lib/rustler_test.ex @@ -83,6 +83,7 @@ defmodule RustlerTest do def threaded_sleep(_), do: err() def send_all(_, _), do: err() + def send(_, _), do: err() def whereis_pid(_), do: err() def sublists(_), do: err() diff --git a/rustler_tests/native/rustler_test/src/lib.rs b/rustler_tests/native/rustler_test/src/lib.rs index bd22ac1f..840fae1c 100644 --- a/rustler_tests/native/rustler_test/src/lib.rs +++ b/rustler_tests/native/rustler_test/src/lib.rs @@ -59,6 +59,7 @@ rustler::init!( test_thread::threaded_fac, test_thread::threaded_sleep, test_env::send_all, + test_env::send, test_env::whereis_pid, test_env::sublists, test_codegen::tuple_echo, diff --git a/rustler_tests/native/rustler_test/src/test_env.rs b/rustler_tests/native/rustler_test/src/test_env.rs index 617105ed..c50b31e8 100644 --- a/rustler_tests/native/rustler_test/src/test_env.rs +++ b/rustler_tests/native/rustler_test/src/test_env.rs @@ -1,4 +1,4 @@ -use rustler::env::{OwnedEnv, SavedTerm}; +use rustler::env::{OwnedEnv, SavedTerm, SendError}; use rustler::types::atom; use rustler::types::list::ListIterator; use rustler::types::LocalPid; @@ -9,12 +9,20 @@ use std::thread; #[rustler::nif] pub fn send_all<'a>(env: Env<'a>, pids: Vec, msg: Term<'a>) -> Term<'a> { for pid in pids { - env.send(&pid, msg); + let _ = env.send(&pid, msg); } msg } +#[rustler::nif] +pub fn send<'a>(env: Env<'a>, pid: LocalPid, msg: Term<'a>) -> Atom { + match env.send(&pid, msg) { + Ok(()) => atom::ok(), + Err(SendError) => atom::error(), + } +} + #[rustler::nif] pub fn whereis_pid<'a>(env: Env<'a>, term: Term<'a>) -> Term<'a> { let result = env.whereis_pid(term); @@ -45,7 +53,7 @@ pub fn sublists<'a>(env: Env<'a>, list: Term<'a>) -> NifResult { thread::spawn(move || { // Use `.send()` to get a `Env` from our `OwnedEnv`, // run some rust code, and finally send the result back to `pid`. - owned_env.send_and_clear(&pid, |env| { + let _ = owned_env.send_and_clear(&pid, |env| { let result: NifResult = (|| { let reversed_list = saved_reversed_list.load(env); let iter: ListIterator = reversed_list.decode()?; diff --git a/rustler_tests/test/env_test.exs b/rustler_tests/test/env_test.exs index d3ea5cc1..d0f16e0b 100644 --- a/rustler_tests/test/env_test.exs +++ b/rustler_tests/test/env_test.exs @@ -67,4 +67,23 @@ defmodule RustlerTest.EnvTest do assert nil == RustlerTest.whereis_pid("not a PID") assert nil == RustlerTest.whereis_pid(:not_a_registered_name) end + + test "send_error" do + task = + Task.async(fn -> + receive do + :exit -> :ok + end + end) + + # A send to an alive process from an alive process should not return an + # error + assert :ok == RustlerTest.send(task.pid, :msg) + assert :ok == RustlerTest.send(task.pid, :exit) + Task.await(task) + + # Once the target process is down, sends should error + assert :error == RustlerTest.send(task.pid, :msg) + assert :error == RustlerTest.send(task.pid, :msg) + end end From f9aa05c6df8a45d3a909769cc3c36366beeb66c1 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 11 Oct 2023 10:40:30 +0200 Subject: [PATCH 03/28] Update Upgrade documentation and Changelog --- CHANGELOG.md | 10 +++++++--- UPGRADE.md | 50 +++++++++++++++++++++++++++++++++++--------------- 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f5dec17..f9c7949b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,14 +11,18 @@ See [`UPGRADE.md`](./UPGRADE.md) for additional help when upgrading to newer ver ### Added +* Return `Result<(), SendError>` from all `send` functions (#239, #563) + ### Changed -* Dropped support for `RUSTLER_NIF_VERSION` -* Deprecated `:rustler_crates` project configuration +* Drop support for `RUSTLER_NIF_VERSION` +* Deprecate `:rustler_crates` project configuration * Mark `use Rustler` module configuration as compile-time * Bump Rust edition to 2021 * Make `:rustler` a compile-time-only dependency (#516, #559) -* Return `Result<(), SendError>` from all `send` functions (#239, #563) +* Use `enif_term_type` to implement `Term::get_type` (#538). Please check the + `UPGRADE` documentation for necessary code changes. +* Raise default NIF version to 2.15 ## [0.29.1] - 2023-06-30 diff --git a/UPGRADE.md b/UPGRADE.md index 5beaaa05..393e23e2 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -2,21 +2,41 @@ This document is intended to simplify upgrading to newer versions by extending the changelog. -## 0.29 -> ... - -`rustler_crates` configuration is deprecated in favor of explicitly passing -options on `use Rustler` or configuring the module in your `config/*.exs` -files. - -Additionally, `Env::send` and `OwnedEnv::send_and_clear` will now return a -`Result`. Updating will thus introduce warnings about unused `Result`s. To -remove the warnings without changing behaviour, the `Result`s can be "used" as -```rust -let _ = env.send(...) -``` -Neither the `Ok` nor the `Err` case carry additional information so far. An -error is returned if either the receiving or the sending process is dead. See -also [enif\_send](https://www.erlang.org/doc/man/erl_nif.html#enif_send). +## 0.29 -> 0.30 + +1. `rustler_crates` configuration is deprecated in favor of explicitly passing + options on `use Rustler` or configuring the module in your `config/*.exs` + files. + +2. `Env::send` and `OwnedEnv::send_and_clear` will now return a `Result`. + Updating will thus introduce warnings about unused `Result`s. To remove the + warnings without changing behaviour, the `Result`s can be "used" as + ```rust + let _ = env.send(...) + ``` + Neither the `Ok` nor the `Err` case carry additional information so far. An + error is returned if either the receiving or the sending process is dead. + See also + [enif\_send](https://www.erlang.org/doc/man/erl_nif.html#enif_send). + +3. As `Term::get_type` is now implemented using `enif_get_type`, some cases of + the `TermType` `enum` are changed, removed, or added: + 1. `EmptyList` is dropped, `List` is returned for both empty and non-empty + lists + 2. `Exception` is dropped + 3. `Number` is split into `Integer` and `Float` (if NIF 2.14 support is + explicitly enforced, only `Float` is returned) + +4. The default NIF version is raised to 2.15 to make use of `enif_get_type`. To + use a compiled NIF with an older version than OTP22, disable the default + features and expliictly use the `nif_version_2_14` feature in the library's + `Cargo.toml`: + ```toml + rustler = { version = "0.30", default-features = false, features = ["derive", "nif_version_2_14"] } + ``` + +5. As noted for the `0.28 -> 0.29` transition below, the environment variable + `RUSTLER_NIF_VERSION` will not be considered anymore from 0.30 onwards. ## 0.28 -> 0.29 From eb53d465de451c7a7c8857df7fb8ba12fa68f86b Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 11 Oct 2023 10:40:34 +0200 Subject: [PATCH 04/28] (release) 0.30.0 --- CHANGELOG.md | 2 +- rustler/Cargo.toml | 4 ++-- rustler_bigint/Cargo.toml | 2 +- rustler_codegen/Cargo.toml | 2 +- rustler_mix/README.md | 2 +- rustler_mix/lib/rustler.ex | 2 +- rustler_mix/mix.exs | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9c7949b..c0aa848d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 See [`UPGRADE.md`](./UPGRADE.md) for additional help when upgrading to newer versions. -## [unreleased] +## [0.30.0] - 2023-10-11 ### Added diff --git a/rustler/Cargo.toml b/rustler/Cargo.toml index 7f3c76ab..e5c53a99 100644 --- a/rustler/Cargo.toml +++ b/rustler/Cargo.toml @@ -2,7 +2,7 @@ name = "rustler" description = "Safe Rust wrappers for creating Erlang NIF functions" repository = "https://github.com/rusterlium/rustler" -version = "0.29.1" # rustler version +version = "0.30.0" # rustler version authors = ["Hansihe "] license = "MIT/Apache-2.0" readme = "../README.md" @@ -19,7 +19,7 @@ nif_version_2_17 = ["nif_version_2_16", "rustler_sys/nif_version_2_17"] [dependencies] lazy_static = "1.4" -rustler_codegen = { path = "../rustler_codegen", version = "0.29.1", optional = true} +rustler_codegen = { path = "../rustler_codegen", version = "0.30.0", optional = true} rustler_sys = { path = "../rustler_sys", version = "~2.3.0" } [package.metadata.release] diff --git a/rustler_bigint/Cargo.toml b/rustler_bigint/Cargo.toml index 16c7b2c1..87664709 100644 --- a/rustler_bigint/Cargo.toml +++ b/rustler_bigint/Cargo.toml @@ -11,4 +11,4 @@ keywords = ["bigint", "Erlang", "Elixir"] [dependencies] num-bigint = {version = "0.4"} -rustler = {path = "../rustler", version = "0.29.1"} +rustler = {path = "../rustler", version = "0.30.0"} diff --git a/rustler_codegen/Cargo.toml b/rustler_codegen/Cargo.toml index cedc7fe2..69db7d66 100644 --- a/rustler_codegen/Cargo.toml +++ b/rustler_codegen/Cargo.toml @@ -2,7 +2,7 @@ name = "rustler_codegen" description = "Compiler plugin for Rustler" repository = "https://github.com/rusterlium/rustler/tree/master/rustler_codegen" -version = "0.29.1" # rustler_codegen version +version = "0.30.0" # rustler_codegen version authors = ["Hansihe "] license = "MIT/Apache-2.0" readme = "../README.md" diff --git a/rustler_mix/README.md b/rustler_mix/README.md index 381db96d..3a78db7a 100644 --- a/rustler_mix/README.md +++ b/rustler_mix/README.md @@ -15,7 +15,7 @@ This package is available on [Hex.pm](https://hex.pm/packages/rustler). To insta ```elixir def deps do [ - {:rustler, "~> 0.29.1", runtime: false} + {:rustler, "~> 0.30.0", runtime: false} ] end ``` diff --git a/rustler_mix/lib/rustler.ex b/rustler_mix/lib/rustler.ex index 9a473512..30ccb685 100644 --- a/rustler_mix/lib/rustler.ex +++ b/rustler_mix/lib/rustler.ex @@ -168,7 +168,7 @@ defmodule Rustler do end @doc false - def rustler_version, do: "0.29.1" + def rustler_version, do: "0.30.0" @doc """ Supported NIF API versions. diff --git a/rustler_mix/mix.exs b/rustler_mix/mix.exs index 1d12cb81..3a86c933 100644 --- a/rustler_mix/mix.exs +++ b/rustler_mix/mix.exs @@ -2,7 +2,7 @@ defmodule Rustler.Mixfile do use Mix.Project @source_url "https://github.com/rusterlium/rustler" - @version "0.29.1" + @version "0.30.0" def project do [ From acc5921652f3a3c5b1ba461971a9767f31a71fcd Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 11 Oct 2023 11:41:53 +0200 Subject: [PATCH 05/28] Add missing UPGRADE docs to hex docs, upgrade ex_doc --- rustler_mix/mix.exs | 1 + rustler_mix/mix.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/rustler_mix/mix.exs b/rustler_mix/mix.exs index 3a86c933..980f5d6b 100644 --- a/rustler_mix/mix.exs +++ b/rustler_mix/mix.exs @@ -47,6 +47,7 @@ defmodule Rustler.Mixfile do [ extras: [ "../CHANGELOG.md", + "../UPGRADE.md", {:"../LICENSE-APACHE", [title: "License (Apache-2.0)"]}, {:"../LICENSE-MIT", [title: "License (MIT)"]}, "README.md" diff --git a/rustler_mix/mix.lock b/rustler_mix/mix.lock index 2a8b8665..c1e3e487 100644 --- a/rustler_mix/mix.lock +++ b/rustler_mix/mix.lock @@ -1,11 +1,11 @@ %{ "earmark": {:hex, :earmark, "1.4.4", "4821b8d05cda507189d51f2caeef370cf1e18ca5d7dfb7d31e9cafe6688106a4", [:mix], [], "hexpm", "1f93aba7340574847c0f609da787f0d79efcab51b044bb6e242cae5aca9d264d"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.25", "2024618731c55ebfcc5439d756852ec4e85978a39d0d58593763924d9a15916f", [:mix], [], "hexpm", "56749c5e1c59447f7b7a23ddb235e4b3defe276afc220a6227237f3efe83f51e"}, - "ex_doc": {:hex, :ex_doc, "0.28.3", "6eea2f69995f5fba94cd6dd398df369fe4e777a47cd887714a0976930615c9e6", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "05387a6a2655b5f9820f3f627450ed20b4325c25977b2ee69bed90af6688e718"}, - "jason": {:hex, :jason, "1.3.0", "fa6b82a934feb176263ad2df0dbd91bf633d4a46ebfdffea0c8ae82953714946", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "53fc1f51255390e0ec7e50f9cb41e751c260d065dcba2bf0d08dc51a4002c2ac"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.37", "2ad73550e27c8946648b06905a57e4d454e4d7229c2dafa72a0348c99d8be5f7", [:mix], [], "hexpm", "6b19783f2802f039806f375610faa22da130b8edc21209d0bff47918bb48360e"}, + "ex_doc": {:hex, :ex_doc, "0.30.6", "5f8b54854b240a2b55c9734c4b1d0dd7bdd41f71a095d42a70445c03cf05a281", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bd48f2ddacf4e482c727f9293d9498e0881597eae6ddc3d9562bd7923375109f"}, + "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"}, - "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, - "toml": {:hex, :toml, "0.6.2", "38f445df384a17e5d382befe30e3489112a48d3ba4c459e543f748c2f25dd4d1", [:mix], [], "hexpm", "d013e45126d74c0c26a38d31f5e8e9b83ea19fc752470feb9a86071ca5a672fa"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, + "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, + "toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"}, } From a82d9422f8dc5848f8f36dc8c9c0209b98d23747 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 11 Oct 2023 16:07:29 +0200 Subject: [PATCH 06/28] Fix linter complaint --- rustler/src/term.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rustler/src/term.rs b/rustler/src/term.rs index 6f26d2f6..27bf5950 100644 --- a/rustler/src/term.rs +++ b/rustler/src/term.rs @@ -151,7 +151,7 @@ impl<'a> Ord for Term<'a> { } impl<'a> PartialOrd for Term<'a> { fn partial_cmp(&self, other: &Term<'a>) -> Option { - Some(cmp(self, other)) + Some(self.cmp(other)) } } From 85582ebfbc633df026027cf767a3607f3d685727 Mon Sep 17 00:00:00 2001 From: Magnus Date: Fri, 20 Oct 2023 20:00:53 +0200 Subject: [PATCH 07/28] ci: Add Elixir v1.15 to the build matrix (#571) * ci: Add Elixir v1.15 to the build matrix * Bump more versions, fix latest being present twice, remove exception --------- Co-authored-by: Benedikt Reinartz --- .github/workflows/main.yml | 12 ++++-------- rustler_tests/lib/rustler_test.ex | 2 +- rustler_tests/test/codegen_test.exs | 2 +- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e219c29b..d08e1698 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,8 +23,8 @@ jobs: - name: Install Erlang/Elixir uses: erlef/setup-beam@v1 with: - otp-version: "25" - elixir-version: "1.14" + otp-version: "26" + elixir-version: "1.15" - name: Check cargo fmt run: cargo fmt --all -- --check @@ -85,7 +85,8 @@ jobs: strategy: matrix: pair: - - { erlang: "26", elixir: "1.14", latest: true } + - { erlang: "26", elixir: "1.15", latest: true } + - { erlang: "26", elixir: "1.14" } - { erlang: "25", elixir: "1.14" } - { erlang: "24", elixir: "1.13" } rust: @@ -95,11 +96,6 @@ jobs: - macos - windows - ubuntu - exclude: - # TODO Remove this once setup-beam is fixed to work around OTP26's - # regression - - os: windows - pair: { erlang: "26", elixir: "1.14", latest: true } runs-on: ${{matrix.os}}-latest steps: diff --git a/rustler_tests/lib/rustler_test.ex b/rustler_tests/lib/rustler_test.ex index 52950aa6..e6776d04 100644 --- a/rustler_tests/lib/rustler_test.ex +++ b/rustler_tests/lib/rustler_test.ex @@ -35,7 +35,7 @@ defmodule RustlerTest do def term_debug_and_reparse(term) do with debug_str <- term_debug(term), - debug_str <- :erlang.binary_to_list(debug_str) ++ '.', + debug_str <- :erlang.binary_to_list(debug_str) ++ ~c".", {:ok, tokens, _} <- :erl_scan.string(debug_str), {:ok, ast} <- :erl_parse.parse_exprs(tokens), {:value, res, _} <- :erl_eval.exprs(ast, %{}) do diff --git a/rustler_tests/test/codegen_test.exs b/rustler_tests/test/codegen_test.exs index 722891d8..1ea7fef9 100644 --- a/rustler_tests/test/codegen_test.exs +++ b/rustler_tests/test/codegen_test.exs @@ -96,7 +96,7 @@ defmodule RustlerTest.CodegenTest do end test "with invalid struct" do - value = %AddException{message: 'this is a charlist', loc: {106, 15}} + value = %AddException{message: ~c"this is a charlist", loc: {106, 15}} assert_raise ErlangError, "Erlang error: \"Could not decode field :message on %AddException{}\"", From 7cbfdd23acf4577f79f8047accafcf6f2e0461a8 Mon Sep 17 00:00:00 2001 From: Magnus Date: Wed, 25 Oct 2023 10:17:38 +0200 Subject: [PATCH 08/28] Expand load data at compile time (#570) --- rustler_mix/lib/rustler.ex | 46 ++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/rustler_mix/lib/rustler.ex b/rustler_mix/lib/rustler.ex index 30ccb685..226ff087 100644 --- a/rustler_mix/lib/rustler.ex +++ b/rustler_mix/lib/rustler.ex @@ -120,35 +120,21 @@ defmodule Rustler do quote do @on_load :rustler_init - @doc false - def rustler_init do - # Remove any old modules that may be loaded so we don't get - # {:error, {:upgrade, 'Upgrade not supported by this NIF library.'}} - :code.purge(__MODULE__) - - {otp_app, path} = @load_from - - load_path = - otp_app - |> Application.app_dir(path) - |> to_charlist() - - load_data = _construct_load_data(@load_data, @load_data_fun) - - :erlang.load_nif(load_path, load_data) - end - - defp _construct_load_data(load_data, load_data_fun) do + defmacrop _construct_load_data do default_load_data_value = unquote(default_load_data_value) default_fun_value = unquote(default_fun_value) - case {load_data, load_data_fun} do + case {@load_data, @load_data_fun} do {load_data, ^default_fun_value} -> - load_data + quote do + unquote(load_data) + end {^default_load_data_value, {module, function}} when is_atom(module) and is_atom(function) -> - apply(module, function, []) + quote do + apply(unquote(module), unquote(function), []) + end {^default_load_data_value, provided_value} -> raise """ @@ -164,6 +150,22 @@ defmodule Rustler do """ end end + + @doc false + def rustler_init do + # Remove any old modules that may be loaded so we don't get + # {:error, {:upgrade, 'Upgrade not supported by this NIF library.'}} + :code.purge(__MODULE__) + + {otp_app, path} = @load_from + + load_path = + otp_app + |> Application.app_dir(path) + |> to_charlist() + + :erlang.load_nif(load_path, _construct_load_data()) + end end end From 73d16bd71dc7504bd5cac8c21ec45c80a386e7b7 Mon Sep 17 00:00:00 2001 From: bcksl <121328003+bcksl@users.noreply.github.com> Date: Mon, 13 Nov 2023 15:46:14 +0200 Subject: [PATCH 09/28] fix(rustler_codegen): warnings for non-snake case atom module names (#573) --- rustler_codegen/src/ex_struct.rs | 1 + rustler_codegen/src/map.rs | 1 + rustler_codegen/src/record.rs | 1 + rustler_codegen/src/tagged_enum.rs | 1 + rustler_codegen/src/unit_enum.rs | 1 + 5 files changed, 5 insertions(+) diff --git a/rustler_codegen/src/ex_struct.rs b/rustler_codegen/src/ex_struct.rs index e051c494..4d720606 100644 --- a/rustler_codegen/src/ex_struct.rs +++ b/rustler_codegen/src/ex_struct.rs @@ -53,6 +53,7 @@ pub fn transcoder_decorator(ast: &syn::DeriveInput, add_exception: bool) -> Toke }; let gen = quote! { + #[allow(non_snake_case)] mod #atoms_module_name { #atom_defs } diff --git a/rustler_codegen/src/map.rs b/rustler_codegen/src/map.rs index 9065d132..5cc9e0d3 100644 --- a/rustler_codegen/src/map.rs +++ b/rustler_codegen/src/map.rs @@ -37,6 +37,7 @@ pub fn transcoder_decorator(ast: &syn::DeriveInput) -> TokenStream { }; let gen = quote! { + #[allow(non_snake_case)] mod #atoms_module_name { #atom_defs } diff --git a/rustler_codegen/src/record.rs b/rustler_codegen/src/record.rs index b568b78f..6d418e87 100644 --- a/rustler_codegen/src/record.rs +++ b/rustler_codegen/src/record.rs @@ -37,6 +37,7 @@ pub fn transcoder_decorator(ast: &syn::DeriveInput) -> TokenStream { }; let gen = quote! { + #[allow(non_snake_case)] mod #atoms_module_name { #atom_defs } diff --git a/rustler_codegen/src/tagged_enum.rs b/rustler_codegen/src/tagged_enum.rs index fe78d331..18678b36 100644 --- a/rustler_codegen/src/tagged_enum.rs +++ b/rustler_codegen/src/tagged_enum.rs @@ -71,6 +71,7 @@ pub fn transcoder_decorator(ast: &syn::DeriveInput) -> TokenStream { }; let gen = quote! { + #[allow(non_snake_case)] mod #atoms_module_name { #atom_defs } diff --git a/rustler_codegen/src/unit_enum.rs b/rustler_codegen/src/unit_enum.rs index 9d443ac7..e0e8ff49 100644 --- a/rustler_codegen/src/unit_enum.rs +++ b/rustler_codegen/src/unit_enum.rs @@ -55,6 +55,7 @@ pub fn transcoder_decorator(ast: &syn::DeriveInput) -> TokenStream { }; let gen = quote! { + #[allow(non_snake_case)] mod #atoms_module_name { #atom_defs } From cfaa727b1d6380886895345a3d3c2c9c15b3cb77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20B=C3=A4renz?= Date: Thu, 9 Nov 2023 14:43:27 +0100 Subject: [PATCH 10/28] Fix typo --- rustler_codegen/src/encode_decode_templates.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rustler_codegen/src/encode_decode_templates.rs b/rustler_codegen/src/encode_decode_templates.rs index 3a59037d..33909718 100644 --- a/rustler_codegen/src/encode_decode_templates.rs +++ b/rustler_codegen/src/encode_decode_templates.rs @@ -31,25 +31,25 @@ pub(crate) fn decoder(ctx: &Context, inner: TokenStream) -> TokenStream { let where_clause = impl_generics.make_where_clause(); for lifetime in lifetimes { - let mut puncated = syn::punctuated::Punctuated::new(); - puncated.push(lifetime.clone()); + let mut punctuated = syn::punctuated::Punctuated::new(); + punctuated.push(lifetime.clone()); let predicate = syn::PredicateLifetime { lifetime: decode_lifetime.clone(), colon_token: syn::token::Colon { spans: [Span::call_site()], }, - bounds: puncated, + bounds: punctuated, }; where_clause.predicates.push(predicate.into()); - let mut puncated = syn::punctuated::Punctuated::new(); - puncated.push(decode_lifetime.clone()); + let mut punctuated = syn::punctuated::Punctuated::new(); + punctuated.push(decode_lifetime.clone()); let predicate = syn::PredicateLifetime { lifetime: lifetime.clone(), colon_token: syn::token::Colon { spans: [Span::call_site()], }, - bounds: puncated, + bounds: punctuated, }; where_clause.predicates.push(predicate.into()); } From 69cf6cada81e56edd44917964d3d9c8d4477f8fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20B=C3=A4renz?= Date: Thu, 9 Nov 2023 15:44:01 +0100 Subject: [PATCH 11/28] Allow generic type parameters --- .../src/encode_decode_templates.rs | 113 +++++++++++++++++- rustler_tests/lib/rustler_test.ex | 2 + rustler_tests/native/rustler_test/src/lib.rs | 4 +- .../native/rustler_test/src/test_codegen.rs | 25 ++++ rustler_tests/test/codegen_test.exs | 12 ++ 5 files changed, 154 insertions(+), 2 deletions(-) diff --git a/rustler_codegen/src/encode_decode_templates.rs b/rustler_codegen/src/encode_decode_templates.rs index 33909718..c7d22fc0 100644 --- a/rustler_codegen/src/encode_decode_templates.rs +++ b/rustler_codegen/src/encode_decode_templates.rs @@ -1,5 +1,6 @@ use proc_macro2::{Span, TokenStream}; use quote::quote; +use syn::{GenericArgument, PathSegment, TraitBound}; use super::context::Context; @@ -55,6 +56,67 @@ pub(crate) fn decoder(ctx: &Context, inner: TokenStream) -> TokenStream { } } + let type_parameters: Vec<_> = generics + .params + .iter() + .filter_map(|g| match g { + syn::GenericParam::Type(t) => Some(t.clone()), + _ => None, + }) + .collect(); + + if !type_parameters.is_empty() { + let where_clause = impl_generics.make_where_clause(); + + for type_parameter in type_parameters { + let mut punctuated = syn::punctuated::Punctuated::new(); + punctuated.push(decode_lifetime.clone().into()); + punctuated.push(syn::TypeParamBound::Trait(TraitBound { + paren_token: None, + modifier: syn::TraitBoundModifier::None, + lifetimes: None, + path: syn::Path { + leading_colon: Some(syn::token::PathSep::default()), + segments: [ + PathSegment { + ident: syn::Ident::new("rustler", Span::call_site()), + arguments: syn::PathArguments::None, + }, + PathSegment { + ident: syn::Ident::new("Decoder", Span::call_site()), + arguments: syn::PathArguments::AngleBracketed( + syn::AngleBracketedGenericArguments { + colon2_token: None, + lt_token: Default::default(), + args: std::iter::once(GenericArgument::Lifetime( + decode_lifetime.clone(), + )) + .collect(), + gt_token: Default::default(), + }, + ), + }, + ] + .iter() + .cloned() + .collect(), + }, + })); + let predicate = syn::PredicateType { + lifetimes: None, + bounded_ty: syn::Type::Path(syn::TypePath { + qself: None, + path: type_parameter.ident.into(), + }), + colon_token: syn::token::Colon { + spans: [Span::call_site()], + }, + bounds: punctuated, + }; + where_clause.predicates.push(predicate.into()); + } + } + let (impl_generics, _, where_clause) = impl_generics.split_for_impl(); quote! { @@ -69,7 +131,56 @@ pub(crate) fn decoder(ctx: &Context, inner: TokenStream) -> TokenStream { pub(crate) fn encoder(ctx: &Context, inner: TokenStream) -> TokenStream { let ident = ctx.ident; - let generics = ctx.generics; + let mut generics = ctx.generics.clone(); + let type_parameters: Vec<_> = generics + .params + .iter() + .filter_map(|g| match g { + syn::GenericParam::Type(t) => Some(t.clone()), + _ => None, + }) + .collect(); + + if !type_parameters.is_empty() { + let where_clause = generics.make_where_clause(); + + for type_parameter in type_parameters { + let mut punctuated = syn::punctuated::Punctuated::new(); + punctuated.push(syn::TypeParamBound::Trait(TraitBound { + paren_token: None, + modifier: syn::TraitBoundModifier::None, + lifetimes: None, + path: syn::Path { + leading_colon: Some(syn::token::PathSep::default()), + segments: [ + PathSegment { + ident: syn::Ident::new("rustler", Span::call_site()), + arguments: syn::PathArguments::None, + }, + PathSegment { + ident: syn::Ident::new("Encoder", Span::call_site()), + arguments: syn::PathArguments::None, + }, + ] + .iter() + .cloned() + .collect(), + }, + })); + let predicate = syn::PredicateType { + lifetimes: None, + bounded_ty: syn::Type::Path(syn::TypePath { + qself: None, + path: type_parameter.ident.into(), + }), + colon_token: syn::token::Colon { + spans: [Span::call_site()], + }, + bounds: punctuated, + }; + where_clause.predicates.push(predicate.into()); + } + } let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); quote! { diff --git a/rustler_tests/lib/rustler_test.ex b/rustler_tests/lib/rustler_test.ex index e6776d04..7de1d85a 100644 --- a/rustler_tests/lib/rustler_test.ex +++ b/rustler_tests/lib/rustler_test.ex @@ -105,6 +105,8 @@ defmodule RustlerTest do def newtype_record_echo(_), do: err() def tuplestruct_record_echo(_), do: err() def reserved_keywords_type_echo(_), do: err() + def generic_struct_echo(_), do: err() + def mk_generic_map(_), do: err() def dirty_io(), do: err() def dirty_cpu(), do: err() diff --git a/rustler_tests/native/rustler_test/src/lib.rs b/rustler_tests/native/rustler_test/src/lib.rs index 840fae1c..6b0e4ece 100644 --- a/rustler_tests/native/rustler_test/src/lib.rs +++ b/rustler_tests/native/rustler_test/src/lib.rs @@ -95,7 +95,9 @@ rustler::init!( test_tuple::maybe_add_one_to_tuple, test_tuple::add_i32_from_tuple, test_tuple::greeting_person_from_tuple, - test_codegen::reserved_keywords::reserved_keywords_type_echo + test_codegen::reserved_keywords::reserved_keywords_type_echo, + test_codegen::generic_types::generic_struct_echo, + test_codegen::generic_types::mk_generic_map, ], load = load ); diff --git a/rustler_tests/native/rustler_test/src/test_codegen.rs b/rustler_tests/native/rustler_test/src/test_codegen.rs index d7671b68..19c58c64 100644 --- a/rustler_tests/native/rustler_test/src/test_codegen.rs +++ b/rustler_tests/native/rustler_test/src/test_codegen.rs @@ -262,3 +262,28 @@ pub mod reserved_keywords { reserved } } + +pub mod generic_types { + use rustler::{NifMap, NifStruct}; + #[derive(NifStruct)] + #[module = "GenericStruct"] + pub struct GenericStruct { + t: T, + } + + #[rustler::nif] + pub fn generic_struct_echo(value: GenericStruct) -> GenericStruct { + value + } + + #[derive(NifMap)] + pub struct GenericMap { + a: T, + b: T, + } + + #[rustler::nif] + pub fn mk_generic_map(value: &str) -> GenericMap<&str> { + GenericMap { a: value, b: value } + } +} diff --git a/rustler_tests/test/codegen_test.exs b/rustler_tests/test/codegen_test.exs index 1ea7fef9..069f8471 100644 --- a/rustler_tests/test/codegen_test.exs +++ b/rustler_tests/test/codegen_test.exs @@ -434,4 +434,16 @@ defmodule RustlerTest.CodegenTest do assert {1} == RustlerTest.reserved_keywords_type_echo({1}) assert {:record, 1} == RustlerTest.reserved_keywords_type_echo({:record, 1}) end + + describe "generic types" do + test "generic struct" do + assert %{__struct__: GenericStruct, t: 1} == + RustlerTest.generic_struct_echo(%{__struct__: GenericStruct, t: 1}) + end + + test "generic map" do + assert %{a: "hello", b: "hello"} == + RustlerTest.mk_generic_map("hello") + end + end end From 7e0699f385dc5195b40b839fef1bc5f5f2e9ca50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20B=C3=A4renz?= Date: Mon, 20 Nov 2023 10:20:32 +0100 Subject: [PATCH 12/28] Simplify where clause and move generic definitions to context --- rustler_codegen/src/context.rs | 27 +- .../src/encode_decode_templates.rs | 248 ++++++++---------- 2 files changed, 133 insertions(+), 142 deletions(-) diff --git a/rustler_codegen/src/context.rs b/rustler_codegen/src/context.rs index 03c31bf6..d157627f 100644 --- a/rustler_codegen/src/context.rs +++ b/rustler_codegen/src/context.rs @@ -1,7 +1,7 @@ use heck::ToSnakeCase; use proc_macro2::{Span, TokenStream}; use quote::quote; -use syn::{Data, Field, Fields, Ident, Lit, Meta, Variant}; +use syn::{Data, Field, Fields, Ident, Lifetime, Lit, Meta, TypeParam, Variant}; use super::RustlerAttr; @@ -14,6 +14,8 @@ pub(crate) struct Context<'a> { pub attrs: Vec, pub ident: &'a proc_macro2::Ident, pub generics: &'a syn::Generics, + pub lifetimes: Vec, + pub type_parameters: Vec, pub variants: Option>, pub struct_fields: Option>, pub is_tuple_struct: bool, @@ -50,10 +52,33 @@ impl<'a> Context<'a> { _ => false, }; + let lifetimes: Vec<_> = ast + .generics + .params + .iter() + .filter_map(|g| match g { + syn::GenericParam::Lifetime(l) => Some(l.lifetime.clone()), + _ => None, + }) + .collect(); + + let type_parameters: Vec<_> = ast + .generics + .params + .iter() + .filter_map(|g| match g { + syn::GenericParam::Type(t) => Some(t.clone()), + // Don't keep lifetimes or generic constants + _ => None, + }) + .collect(); + Self { attrs, ident: &ast.ident, generics: &ast.generics, + lifetimes, + type_parameters, variants, struct_fields, is_tuple_struct, diff --git a/rustler_codegen/src/encode_decode_templates.rs b/rustler_codegen/src/encode_decode_templates.rs index c7d22fc0..f1e005ad 100644 --- a/rustler_codegen/src/encode_decode_templates.rs +++ b/rustler_codegen/src/encode_decode_templates.rs @@ -12,15 +12,6 @@ pub(crate) fn decoder(ctx: &Context, inner: TokenStream) -> TokenStream { // The Decoder uses a special lifetime '__rustler_decode_lifetime. We need to ensure that all // other lifetimes are bound to this lifetime: As we decode from a term (which has a lifetime), // references to that term may not outlive the term itself. - let lifetimes: Vec<_> = generics - .params - .iter() - .filter_map(|g| match g { - syn::GenericParam::Lifetime(l) => Some(l.lifetime.clone()), - _ => None, - }) - .collect(); - let mut impl_generics = generics.clone(); let decode_lifetime = syn::Lifetime::new("'__rustler_decode_lifetime", Span::call_site()); let lifetime_def = syn::LifetimeParam::new(decode_lifetime.clone()); @@ -28,93 +19,78 @@ pub(crate) fn decoder(ctx: &Context, inner: TokenStream) -> TokenStream { .params .push(syn::GenericParam::Lifetime(lifetime_def)); - if !lifetimes.is_empty() { - let where_clause = impl_generics.make_where_clause(); - - for lifetime in lifetimes { - let mut punctuated = syn::punctuated::Punctuated::new(); - punctuated.push(lifetime.clone()); - let predicate = syn::PredicateLifetime { - lifetime: decode_lifetime.clone(), - colon_token: syn::token::Colon { - spans: [Span::call_site()], - }, - bounds: punctuated, - }; - where_clause.predicates.push(predicate.into()); - - let mut punctuated = syn::punctuated::Punctuated::new(); - punctuated.push(decode_lifetime.clone()); - let predicate = syn::PredicateLifetime { - lifetime: lifetime.clone(), - colon_token: syn::token::Colon { - spans: [Span::call_site()], - }, - bounds: punctuated, - }; - where_clause.predicates.push(predicate.into()); - } + let where_clause = impl_generics.make_where_clause(); + + for lifetime in ctx.lifetimes.iter() { + let mut punctuated = syn::punctuated::Punctuated::new(); + punctuated.push(lifetime.clone()); + let predicate = syn::PredicateLifetime { + lifetime: decode_lifetime.clone(), + colon_token: syn::token::Colon { + spans: [Span::call_site()], + }, + bounds: punctuated, + }; + where_clause.predicates.push(predicate.into()); + + let mut punctuated = syn::punctuated::Punctuated::new(); + punctuated.push(decode_lifetime.clone()); + let predicate = syn::PredicateLifetime { + lifetime: lifetime.clone(), + colon_token: syn::token::Colon { + spans: [Span::call_site()], + }, + bounds: punctuated, + }; + where_clause.predicates.push(predicate.into()); } - let type_parameters: Vec<_> = generics - .params - .iter() - .filter_map(|g| match g { - syn::GenericParam::Type(t) => Some(t.clone()), - _ => None, - }) - .collect(); - - if !type_parameters.is_empty() { - let where_clause = impl_generics.make_where_clause(); - - for type_parameter in type_parameters { - let mut punctuated = syn::punctuated::Punctuated::new(); - punctuated.push(decode_lifetime.clone().into()); - punctuated.push(syn::TypeParamBound::Trait(TraitBound { - paren_token: None, - modifier: syn::TraitBoundModifier::None, - lifetimes: None, - path: syn::Path { - leading_colon: Some(syn::token::PathSep::default()), - segments: [ - PathSegment { - ident: syn::Ident::new("rustler", Span::call_site()), - arguments: syn::PathArguments::None, - }, - PathSegment { - ident: syn::Ident::new("Decoder", Span::call_site()), - arguments: syn::PathArguments::AngleBracketed( - syn::AngleBracketedGenericArguments { - colon2_token: None, - lt_token: Default::default(), - args: std::iter::once(GenericArgument::Lifetime( - decode_lifetime.clone(), - )) - .collect(), - gt_token: Default::default(), - }, - ), - }, - ] - .iter() - .cloned() - .collect(), - }, - })); - let predicate = syn::PredicateType { - lifetimes: None, - bounded_ty: syn::Type::Path(syn::TypePath { - qself: None, - path: type_parameter.ident.into(), - }), - colon_token: syn::token::Colon { - spans: [Span::call_site()], - }, - bounds: punctuated, - }; - where_clause.predicates.push(predicate.into()); - } + for type_parameter in ctx.type_parameters.iter() { + let mut punctuated = syn::punctuated::Punctuated::new(); + punctuated.push(decode_lifetime.clone().into()); + punctuated.push(syn::TypeParamBound::Trait(TraitBound { + paren_token: None, + modifier: syn::TraitBoundModifier::None, + lifetimes: None, + path: syn::Path { + leading_colon: Some(syn::token::PathSep::default()), + segments: [ + PathSegment { + ident: syn::Ident::new("rustler", Span::call_site()), + arguments: syn::PathArguments::None, + }, + PathSegment { + ident: syn::Ident::new("Decoder", Span::call_site()), + arguments: syn::PathArguments::AngleBracketed( + syn::AngleBracketedGenericArguments { + colon2_token: None, + lt_token: Default::default(), + args: std::iter::once(GenericArgument::Lifetime( + decode_lifetime.clone(), + )) + .collect(), + gt_token: Default::default(), + }, + ), + }, + ] + .iter() + .cloned() + .collect(), + }, + })); + let predicate = syn::PredicateType { + lifetimes: None, + bounded_ty: syn::Type::Path(syn::TypePath { + qself: None, + path: type_parameter.clone().ident.into(), + }), + colon_token: syn::token::Colon { + spans: [Span::call_site()], + }, + bounds: punctuated, + }; + where_clause.predicates.push(predicate.into()); } let (impl_generics, _, where_clause) = impl_generics.split_for_impl(); @@ -132,54 +108,44 @@ pub(crate) fn decoder(ctx: &Context, inner: TokenStream) -> TokenStream { pub(crate) fn encoder(ctx: &Context, inner: TokenStream) -> TokenStream { let ident = ctx.ident; let mut generics = ctx.generics.clone(); - let type_parameters: Vec<_> = generics - .params - .iter() - .filter_map(|g| match g { - syn::GenericParam::Type(t) => Some(t.clone()), - _ => None, - }) - .collect(); - - if !type_parameters.is_empty() { - let where_clause = generics.make_where_clause(); - for type_parameter in type_parameters { - let mut punctuated = syn::punctuated::Punctuated::new(); - punctuated.push(syn::TypeParamBound::Trait(TraitBound { - paren_token: None, - modifier: syn::TraitBoundModifier::None, - lifetimes: None, - path: syn::Path { - leading_colon: Some(syn::token::PathSep::default()), - segments: [ - PathSegment { - ident: syn::Ident::new("rustler", Span::call_site()), - arguments: syn::PathArguments::None, - }, - PathSegment { - ident: syn::Ident::new("Encoder", Span::call_site()), - arguments: syn::PathArguments::None, - }, - ] - .iter() - .cloned() - .collect(), - }, - })); - let predicate = syn::PredicateType { - lifetimes: None, - bounded_ty: syn::Type::Path(syn::TypePath { - qself: None, - path: type_parameter.ident.into(), - }), - colon_token: syn::token::Colon { - spans: [Span::call_site()], - }, - bounds: punctuated, - }; - where_clause.predicates.push(predicate.into()); - } + let where_clause = generics.make_where_clause(); + + for type_parameter in ctx.type_parameters.iter() { + let mut punctuated = syn::punctuated::Punctuated::new(); + punctuated.push(syn::TypeParamBound::Trait(TraitBound { + paren_token: None, + modifier: syn::TraitBoundModifier::None, + lifetimes: None, + path: syn::Path { + leading_colon: Some(syn::token::PathSep::default()), + segments: [ + PathSegment { + ident: syn::Ident::new("rustler", Span::call_site()), + arguments: syn::PathArguments::None, + }, + PathSegment { + ident: syn::Ident::new("Encoder", Span::call_site()), + arguments: syn::PathArguments::None, + }, + ] + .iter() + .cloned() + .collect(), + }, + })); + let predicate = syn::PredicateType { + lifetimes: None, + bounded_ty: syn::Type::Path(syn::TypePath { + qself: None, + path: type_parameter.ident.clone().into(), + }), + colon_token: syn::token::Colon { + spans: [Span::call_site()], + }, + bounds: punctuated, + }; + where_clause.predicates.push(predicate.into()); } let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); From f93221cbd0a4528b8846321a2d310efb5e1af68d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20B=C3=A4renz?= Date: Tue, 21 Nov 2023 08:43:27 +0100 Subject: [PATCH 13/28] Fix error messages (#579) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Manuel Bärenz --- rustler_codegen/src/context.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rustler_codegen/src/context.rs b/rustler_codegen/src/context.rs index 03c31bf6..b7ca49bf 100644 --- a/rustler_codegen/src/context.rs +++ b/rustler_codegen/src/context.rs @@ -187,7 +187,7 @@ impl<'a> Context<'a> { } } } - panic!("Cannot parse module") + panic!("Cannot parse tag") } fn try_parse_module(meta: &Meta) -> Option> { @@ -201,6 +201,6 @@ impl<'a> Context<'a> { } } } - panic!("Cannot parse tag") + panic!("Cannot parse module") } } From 6e803c00e67b9df9bbc73375662c4f27981dd0f2 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Fri, 5 Jan 2024 18:29:57 +0000 Subject: [PATCH 14/28] Fix rustler_sys tests on 64bit ARM (#584) Whether c_char is i8 or u8 is platform-dependent (https://doc.rust-lang.org/std/ffi/type.c_char.html), this is the only place where it is relevant so far, though. We could adjust the `rustler` struct and type mappings as well. --- rustler_sys/src/initmacro.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/rustler_sys/src/initmacro.rs b/rustler_sys/src/initmacro.rs index 23ab5b90..2160cfcf 100644 --- a/rustler_sys/src/initmacro.rs +++ b/rustler_sys/src/initmacro.rs @@ -279,7 +279,7 @@ mod initmacro_namespace_tests { // use rustler_sys_api::*; use crate::rustler_sys_api; - use std::ffi::{CStr, CString}; + use std::ffi::{c_char, CStr, CString}; use std::ptr; use std::slice; @@ -327,7 +327,7 @@ mod initmacro_namespace_tests { let funcs = unsafe { slice::from_raw_parts(entry.funcs, entry.num_of_funcs as usize) }; assert_eq!(1, funcs.len()); assert_eq!(CString::new("raw1").unwrap().as_ref(), unsafe { - CStr::from_ptr(funcs[0].name as *const i8) + CStr::from_ptr(funcs[0].name as *const c_char) }); assert_eq!(3, funcs[0].arity); assert_eq!(28, unsafe { @@ -342,7 +342,7 @@ mod initmacro_namespace_tests { let funcs = unsafe { slice::from_raw_parts(entry.funcs, entry.num_of_funcs as usize) }; assert_eq!(1, funcs.len()); assert_eq!(CString::new("sliced").unwrap().as_ref(), unsafe { - CStr::from_ptr(funcs[0].name as *const i8) + CStr::from_ptr(funcs[0].name as *const c_char) }); assert_eq!(6, funcs[0].arity); assert_eq!(34, unsafe { @@ -474,7 +474,7 @@ mod initmacro_tests { fn modname() { let entry = get_entry!("bananas", []); assert_eq!(CString::new("bananas").unwrap().as_ref(), unsafe { - CStr::from_ptr(entry.name as *const i8) + CStr::from_ptr(entry.name as *const c_char) }); } @@ -484,7 +484,7 @@ mod initmacro_tests { let funcs = unsafe { slice::from_raw_parts(entry.funcs, entry.num_of_funcs as usize) }; assert_eq!(1, funcs.len()); assert_eq!(CString::new("raw1").unwrap().as_ref(), unsafe { - CStr::from_ptr(funcs[0].name as *const i8) + CStr::from_ptr(funcs[0].name as *const c_char) }); assert_eq!(3, funcs[0].arity); assert_eq!(28, unsafe { @@ -505,7 +505,7 @@ mod initmacro_tests { let funcs = unsafe { slice::from_raw_parts(entry.funcs, entry.num_of_funcs as usize) }; assert_eq!(2, funcs.len()); assert_eq!(CString::new("raw1").unwrap().as_ref(), unsafe { - CStr::from_ptr(funcs[0].name as *const i8) + CStr::from_ptr(funcs[0].name as *const c_char) }); assert_eq!(3, funcs[0].arity); assert_eq!(28, unsafe { @@ -513,7 +513,7 @@ mod initmacro_tests { }); assert_eq!(0, funcs[0].flags); assert_eq!(CString::new("raw2").unwrap().as_ref(), unsafe { - CStr::from_ptr(funcs[1].name as *const i8) + CStr::from_ptr(funcs[1].name as *const c_char) }); assert_eq!(33, funcs[1].arity); assert_eq!(44, unsafe { @@ -528,7 +528,7 @@ mod initmacro_tests { let funcs = unsafe { slice::from_raw_parts(entry.funcs, entry.num_of_funcs as usize) }; assert_eq!(1, funcs.len()); assert_eq!(CString::new("closure").unwrap().as_ref(), unsafe { - CStr::from_ptr(funcs[0].name as *const i8) + CStr::from_ptr(funcs[0].name as *const c_char) }); assert_eq!(5, funcs[0].arity); assert_eq!(52, unsafe { @@ -543,7 +543,7 @@ mod initmacro_tests { let funcs = unsafe { slice::from_raw_parts(entry.funcs, entry.num_of_funcs as usize) }; assert_eq!(1, funcs.len()); assert_eq!(CString::new("sliced").unwrap().as_ref(), unsafe { - CStr::from_ptr(funcs[0].name as *const i8) + CStr::from_ptr(funcs[0].name as *const c_char) }); assert_eq!(6, funcs[0].arity); assert_eq!(34, unsafe { @@ -566,7 +566,7 @@ mod initmacro_tests { let funcs = unsafe { slice::from_raw_parts(entry.funcs, entry.num_of_funcs as usize) }; assert_eq!(CString::new("legacymod").unwrap().as_ref(), unsafe { - CStr::from_ptr(entry.name as *const i8) + CStr::from_ptr(entry.name as *const c_char) }); assert_eq!(114, unsafe { @@ -579,7 +579,7 @@ mod initmacro_tests { assert_eq!(2, funcs.len()); assert_eq!(CString::new("cnif_1").unwrap().as_ref(), unsafe { - CStr::from_ptr(funcs[0].name as *const i8) + CStr::from_ptr(funcs[0].name as *const c_char) }); assert_eq!(7, funcs[0].arity); assert_eq!(38, unsafe { @@ -588,7 +588,7 @@ mod initmacro_tests { assert_eq!(ERL_NIF_DIRTY_JOB_IO_BOUND, funcs[0].flags); assert_eq!(CString::new("cnif_2").unwrap().as_ref(), unsafe { - CStr::from_ptr(funcs[1].name as *const i8) + CStr::from_ptr(funcs[1].name as *const c_char) }); assert_eq!(8, funcs[1].arity); assert_eq!(57, unsafe { @@ -620,7 +620,7 @@ mod initmacro_tests { entry.load.unwrap()(ptr::null_mut(), ptr::null_mut(), 0) }); assert_eq!(CString::new("unsafe_nif").unwrap().as_ref(), unsafe { - CStr::from_ptr(funcs[0].name as *const i8) + CStr::from_ptr(funcs[0].name as *const c_char) }); assert_eq!(3, funcs[0].arity); assert_eq!(46, unsafe { From 52524baf852e6334d3c055f199b6539f306f3de8 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 10 Feb 2024 23:08:40 +0100 Subject: [PATCH 15/28] Move cargo config as the non-toml path is deprecated --- .cargo/{config => config.toml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .cargo/{config => config.toml} (100%) diff --git a/.cargo/config b/.cargo/config.toml similarity index 100% rename from .cargo/config rename to .cargo/config.toml From 5d7874472302c1a696c6800f9d938be0550cd02c Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 10 Feb 2024 23:13:20 +0100 Subject: [PATCH 16/28] Ignore intentionally unused code in test --- rustler_tests/native/rustler_test/src/test_codegen.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rustler_tests/native/rustler_test/src/test_codegen.rs b/rustler_tests/native/rustler_test/src/test_codegen.rs index 19c58c64..5cff1484 100644 --- a/rustler_tests/native/rustler_test/src/test_codegen.rs +++ b/rustler_tests/native/rustler_test/src/test_codegen.rs @@ -5,6 +5,7 @@ use rustler::{ }; /// A trait for testing the ambiguity of `encode` and `decode`. +#[allow(dead_code)] pub trait EmptyTranscoder { fn encode(&self); fn decode(); From c42919c16fe1fb32ea466ee4bf6d23b0527a1f8a Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 10 Feb 2024 23:29:58 +0100 Subject: [PATCH 17/28] Remove old and very likely completely unused initmacro --- rustler_sys/src/initmacro.rs | 631 ----------------------------------- rustler_sys/src/lib.rs | 122 ------- 2 files changed, 753 deletions(-) delete mode 100644 rustler_sys/src/initmacro.rs diff --git a/rustler_sys/src/initmacro.rs b/rustler_sys/src/initmacro.rs deleted file mode 100644 index 2160cfcf..00000000 --- a/rustler_sys/src/initmacro.rs +++ /dev/null @@ -1,631 +0,0 @@ -/// Implement exported module init function needed by the Erlang runtime. -/// -/// See [the module level documentation](index.html) for usage of `nif_init!`. -/// -/// The pre-0.5.5 `nif_init!` format is deprecated but still supported. -/// An example of this format is ... -/// -/// ```rust,ignore -/// nif_init!(b"my_nif_module\0", Some(load), None, None, None, -/// nif!(b"my_nif_fun1\0", 1, my_nif_fun1), -/// nif!(b"my_dirty_fun2\0", 1, my_dirty_fun2, ERL_NIF_DIRTY_JOB_CPU_BOUND) -/// ); -/// ``` -#[macro_export] -macro_rules! nif_init { - ($($rest:tt)*) => ( - platform_nif_init!( - get_entry!($($rest)*) - ); - ) -} - -/// Platform specific NIF module initialization. -/// -/// This macro is intended for higher level NIF libraries and not for direct -/// users of `erlang_nif-sys`. See implementation of `nif_init!` for usage. -#[macro_export] -macro_rules! platform_nif_init { - ($entry:expr) => { - #[cfg(unix)] - #[no_mangle] - pub extern "C" fn nif_init() -> *const $crate::rustler_sys_api::ErlNifEntry { - $entry - } - - #[cfg(windows)] - #[no_mangle] - pub extern "C" fn nif_init( - callbacks: *mut $crate::rustler_sys_api::TWinDynNifCallbacks, - ) -> *const $crate::rustler_sys_api::ErlNifEntry { - unsafe { - WIN_DYN_NIF_CALLBACKS = Some(*callbacks); - } - //std::ptr::copy_nonoverlapping(callbacks, &WinDynNifCallbacks, std::mem::size_of()); - $entry - } - }; -} - -/// Wrapper to deliver NIF args as Rust slice. -/// -/// A macro wrapper that combines the argc and args parameters into a -/// more Rustic slice (`&[ERL_NIF_TERM]`). On release builds this macro -/// incurs zero overhead. -/// -/// -/// # Examples -/// ``` -/// use rustler_sys::*; -/// use std::mem; -/// -/// nif_init!("mymod", [ -/// ("native_add", 2, slice_args!(native_add)) -/// ]); -/// -/// fn native_add(env: *mut ErlNifEnv, -/// args: &[ERL_NIF_TERM]) -> ERL_NIF_TERM { -/// unsafe { -/// let mut a: c_int = mem::uninitialized(); -/// let mut b: c_int = mem::uninitialized(); -/// if args.len() == 2 && -/// 0 != enif_get_int(env, args[0], &mut a) && -/// 0 != enif_get_int(env, args[1], &mut b) { -/// enif_make_int(env, a+b) -/// } -/// else { -/// enif_make_badarg(env) -/// } -/// } -/// } -/// # fn main(){} //3 -#[macro_export] -macro_rules! slice_args { - ($f:expr) => {{ - use $crate::rustler_sys_api as ens; - |env: *mut ens::ErlNifEnv, - argc: ens::c_int, - args: *const ens::ERL_NIF_TERM| - -> ens::ERL_NIF_TERM { $f(env, std::slice::from_raw_parts(args, argc as usize)) } - }}; -} - -/// Internal macro for implementing a ErlNifEntry-creating function. -#[doc(hidden)] -#[macro_export] -macro_rules! get_entry { - // add default options if elided - ( $module:expr, $funcs_tt:tt) => ( get_entry!($module, $funcs_tt, {}) ); - - // strip trailing comma in funcs - ( $module:expr, [$($funcs:tt),+,], $inits_tt:tt ) => ( get_entry!($module, [$($funcs),*], $inits_tt) ); - - ( $module:expr, [$($funcs:tt),*], {$($inits:tt)*} ) => ( - { // start closure - use $crate::rustler_sys_api as ens; - const FUNCS: &'static [ens::ErlNifFunc] = &[$(make_func_entry!($funcs)),*]; - - // initialize as much as possible statically - static mut ENTRY: ens::ErlNifEntry = ens::ErlNifEntry{ - major : ens::NIF_MAJOR_VERSION, - minor : ens::NIF_MINOR_VERSION, - name : concat!($module, "\0") as *const str as *const u8, - num_of_funcs : 0 as ens::c_int, - funcs : &[] as *const ens::ErlNifFunc, - load: None, - reload: None, - upgrade: None, - unload: None, - vm_variant : b"beam.vanilla\0" as *const u8, - options: ens::ERL_NIF_ENTRY_OPTIONS, - sizeof_ErlNifResourceTypeInit: 0, - }; - - // get a safe mutable reference once to avoid repeated unsafe - let entry = unsafe { &mut ENTRY }; - - // perform dynamic insertions - entry.num_of_funcs = FUNCS.len() as ens::c_int; - entry.funcs = FUNCS.as_ptr(); - set_optionals!(entry, $($inits)*); - entry.sizeof_ErlNifResourceTypeInit = std::mem::size_of::(); - entry // return static entry reference - } // end closure - ); - - // For legacy nif_init!() invocation, deprecated - ($module:expr, $load:expr, $reload:expr, $upgrade:expr, $unload:expr, $($func:expr),* ) => ( - { // start closure - use $crate::rustler_sys_api as ens; - const FUNCS: &'static [ens::ErlNifFunc] = &[$($func),*]; - static mut ENTRY: ens::ErlNifEntry = ens::ErlNifEntry{ - major : ens::NIF_MAJOR_VERSION, - minor : ens::NIF_MINOR_VERSION, - name : $module as *const u8, - num_of_funcs : 0 as ens::c_int, - funcs : &[] as *const ens::ErlNifFunc, - load : $load, - reload : $reload, - upgrade : $upgrade, - unload : $unload, - vm_variant : b"beam.vanilla\0" as *const u8, - options: ens::ERL_NIF_ENTRY_OPTIONS, - sizeof_ErlNifResourceTypeInit: 0, - }; - // get a safe mutable reference once to avoid repeated unsafe - let entry = unsafe { &mut ENTRY }; - - // perform dynamic insertions - entry.num_of_funcs = FUNCS.len() as ens::c_int; - entry.funcs = FUNCS.as_ptr(); - entry.sizeof_ErlNifResourceTypeInit = std::mem::size_of::(); - entry // return static entry reference - } // end closure - ); -} - -/// Create ErlNifFunc structure. Use inside `nif_init!`. (Deprecated) -/// -/// This is deprecated; see [the module level documentation](index.html) -/// for current `nif_init!` syntax. -#[macro_export] -macro_rules! nif { - ($name:expr, $arity:expr, $function:expr, $flags:expr) => { - ens::ErlNifFunc { - name: $name as *const u8, - arity: $arity, - function: $function, - flags: $flags, - } - }; - - ($name:expr, $arity:expr, $function:expr) => { - nif!($name, $arity, $function, 0) - }; -} - -/// Internal macro to create an ErlNifEntry-creating function. -#[doc(hidden)] -#[macro_export] -macro_rules! make_func_entry { - (($name:expr, $arity:expr, $function:expr, $flags:expr)) => { - ens::ErlNifFunc { - name: concat!($name, "\0") as *const str as *const u8, - arity: $arity, - function: { - unsafe extern "C" fn wrapper( - env: *mut ens::ErlNifEnv, - argc: ens::c_int, - args: *const ens::ERL_NIF_TERM, - ) -> ens::ERL_NIF_TERM { - let func = $function; - func(env, argc, args) - } - wrapper - }, - flags: $flags, - } - }; - - (($name:expr, $arity:expr, $function:expr)) => { - make_func_entry!(($name, $arity, $function, 0)) - }; -} - -/// Internal macro to deal with optional init functions. -#[doc(hidden)] -#[macro_export] -macro_rules! set_optionals { - ($entry:ident, $fname:ident: $val:expr, $($rest:tt)*) => ( - set_optional!($entry, $fname, $val); - set_optionals!($entry, $($rest)*); - ); - ($entry:ident, $fname:ident: $val:expr) => ( - set_optional!($entry, $fname, $val); - ); - //($entry:ident$($rest:tt)*) => ($($rest)*); - ($entry:ident,) => (); -} - -/// Internal macro to deal with optional init functions. -#[doc(hidden)] -#[macro_export] -macro_rules! set_optional { - ($entry:ident, load, $val:expr) => {{ - unsafe extern "C" fn wrapper( - env: *mut ens::ErlNifEnv, - priv_data: *mut *mut ens::c_void, - load_info: ens::ERL_NIF_TERM, - ) -> ens::c_int { - let func = $val; - func(env, priv_data, load_info) - } - $entry.load = Some(wrapper); - }}; - ($entry:ident, reload, $val:expr) => {{ - unsafe extern "C" fn wrapper( - env: *mut ens::ErlNifEnv, - priv_data: *mut *mut ens::c_void, - load_info: ens::ERL_NIF_TERM, - ) -> ens::c_int { - $val(env, priv_data, load_info) - } - $entry.reload = Some(wrapper); - }}; - ($entry:ident, upgrade, $val:expr) => {{ - unsafe extern "C" fn wrapper( - env: *mut ens::ErlNifEnv, - priv_data: *mut *mut ens::c_void, - old_priv_data: *mut *mut ens::c_void, - load_info: ens::ERL_NIF_TERM, - ) -> ens::c_int { - $val(env, priv_data, old_priv_data, load_info) - } - $entry.upgrade = Some(wrapper); - }}; - ($entry:ident, unload, $val:expr) => {{ - unsafe extern "C" fn wrapper(env: *mut ens::ErlNifEnv, priv_data: *mut ens::c_void) { - $val(env, priv_data) - } - $entry.unload = Some(wrapper); - }}; -} - -#[cfg(test)] -#[allow(clippy::redundant_closure_call)] -mod initmacro_namespace_tests { - - // explicitly disable for this test: - // use rustler_sys_api::*; - use crate::rustler_sys_api; - - use std::ffi::{c_char, CStr, CString}; - use std::ptr; - use std::slice; - - // Initializer tests - fn load( - _env: *mut rustler_sys_api::ErlNifEnv, - _priv_data: *mut *mut rustler_sys_api::c_void, - _load_info: rustler_sys_api::ERL_NIF_TERM, - ) -> rustler_sys_api::c_int { - 14 - } - - fn unload(_env: *mut rustler_sys_api::ErlNifEnv, _priv_data: *mut rustler_sys_api::c_void) {} - - fn raw_nif1( - _env: *mut rustler_sys_api::ErlNifEnv, - argc: rustler_sys_api::c_int, - _args: *const rustler_sys_api::ERL_NIF_TERM, - ) -> rustler_sys_api::ERL_NIF_TERM { - (argc * 7) as usize - } - - fn slice_nif( - _env: *mut rustler_sys_api::ErlNifEnv, - args: &[rustler_sys_api::ERL_NIF_TERM], - ) -> rustler_sys_api::ERL_NIF_TERM { - args.len() * 17 - } - - #[test] - fn opt_some2() { - let entry = get_entry!("empty", [], {load: load, unload:unload}); - assert_eq!(0, entry.num_of_funcs); - assert_eq!(14, unsafe { - entry.load.unwrap()(ptr::null_mut(), ptr::null_mut(), 0) - }); - assert_eq!(None, entry.reload); - assert_eq!(None, entry.upgrade); - unsafe { entry.unload.unwrap()(ptr::null_mut(), ptr::null_mut()) }; // shouldn't panic or crash - } - - #[test] - fn nif1() { - let entry = get_entry!("nifs", [("raw1", 3, raw_nif1)]); - let funcs = unsafe { slice::from_raw_parts(entry.funcs, entry.num_of_funcs as usize) }; - assert_eq!(1, funcs.len()); - assert_eq!(CString::new("raw1").unwrap().as_ref(), unsafe { - CStr::from_ptr(funcs[0].name as *const c_char) - }); - assert_eq!(3, funcs[0].arity); - assert_eq!(28, unsafe { - (funcs[0].function)(ptr::null_mut(), 4, ptr::null_mut()) - }); - assert_eq!(0, funcs[0].flags); - } - - #[test] - fn nif_wrapped() { - let entry = get_entry!("nifs", [("sliced", 6, slice_args!(slice_nif))]); - let funcs = unsafe { slice::from_raw_parts(entry.funcs, entry.num_of_funcs as usize) }; - assert_eq!(1, funcs.len()); - assert_eq!(CString::new("sliced").unwrap().as_ref(), unsafe { - CStr::from_ptr(funcs[0].name as *const c_char) - }); - assert_eq!(6, funcs[0].arity); - assert_eq!(34, unsafe { - (funcs[0].function)(ptr::null_mut(), 2, ptr::null_mut()) - }); - assert_eq!(0, funcs[0].flags); - } -} - -#[cfg(test)] -mod initmacro_tests { - use crate::rustler_sys_api::*; - - use std::ffi::{CStr, CString}; - use std::ptr; - use std::slice; - - // Initializer tests - - fn load(_env: *mut ErlNifEnv, _priv_data: *mut *mut c_void, _load_info: ERL_NIF_TERM) -> c_int { - 14 - } - - fn unload(_env: *mut ErlNifEnv, _priv_data: *mut c_void) {} - - fn raw_nif1(_env: *mut ErlNifEnv, argc: c_int, _args: *const ERL_NIF_TERM) -> ERL_NIF_TERM { - (argc * 7) as usize - } - - fn raw_nif2(_env: *mut ErlNifEnv, argc: c_int, _args: *const ERL_NIF_TERM) -> ERL_NIF_TERM { - (argc * 11) as usize - } - - fn slice_nif(_env: *mut ErlNifEnv, args: &[ERL_NIF_TERM]) -> ERL_NIF_TERM { - args.len() * 17 - } - - extern "C" fn c_load( - _env: *mut ErlNifEnv, - _priv_data: *mut *mut c_void, - _load_info: ERL_NIF_TERM, - ) -> c_int { - 114 - } - - extern "C" fn c_unload(_env: *mut ErlNifEnv, _priv_data: *mut c_void) {} - - extern "C" fn c_nif1( - _env: *mut ErlNifEnv, - argc: c_int, - _args: *const ERL_NIF_TERM, - ) -> ERL_NIF_TERM { - (argc * 19) as usize - } - - unsafe fn unsafe_load( - _env: *mut ErlNifEnv, - _priv_data: *mut *mut c_void, - _load_info: ERL_NIF_TERM, - ) -> c_int { - 15 - } - - unsafe fn unsafe_nif( - _env: *mut ErlNifEnv, - argc: c_int, - _args: *const ERL_NIF_TERM, - ) -> ERL_NIF_TERM { - (argc * 23) as usize - } - - #[test] - fn opt_empty() { - let entry = get_entry!("empty", []); - assert_eq!(0, entry.num_of_funcs); - assert_eq!(None, entry.load); - assert_eq!(None, entry.reload); - assert_eq!(None, entry.upgrade); - assert_eq!(None, entry.unload); - } - - #[test] - fn opt_some1() { - let entry = get_entry!("empty", [], { load: load }); - assert_eq!(0, entry.num_of_funcs); - assert_eq!(14, unsafe { - entry.load.unwrap()(ptr::null_mut(), ptr::null_mut(), 0) - }); - assert_eq!(None, entry.reload); - assert_eq!(None, entry.upgrade); - assert_eq!(None, entry.unload); - } - - #[test] - fn opt_some2() { - let entry = get_entry!("empty", [], {load: load, unload:unload}); - assert_eq!(0, entry.num_of_funcs); - assert_eq!(14, unsafe { - entry.load.unwrap()(ptr::null_mut(), ptr::null_mut(), 0) - }); - assert_eq!(None, entry.reload); - assert_eq!(None, entry.upgrade); - unsafe { entry.unload.unwrap()(ptr::null_mut(), ptr::null_mut()) }; // shouldn't panic or crash - } - - #[test] - fn opt_some2b() { - // optionals in different order as opt_some2 - let entry = get_entry!("empty", [], {unload:unload, load: load}); - assert_eq!(0, entry.num_of_funcs); - assert_eq!(14, unsafe { - entry.load.unwrap()(ptr::null_mut(), ptr::null_mut(), 0) - }); - assert_eq!(None, entry.reload); - assert_eq!(None, entry.upgrade); - unsafe { entry.unload.unwrap()(ptr::null_mut(), ptr::null_mut()) }; // shouldn't panic or crash - } - - #[test] - fn opt_closure() { - // optionals in different order as opt_some2 - let entry = get_entry!("empty", [], {load: |_,_,_|15}); - assert_eq!(15, unsafe { - entry.load.unwrap()(ptr::null_mut(), ptr::null_mut(), 0) - }); - } - - #[test] - fn modname() { - let entry = get_entry!("bananas", []); - assert_eq!(CString::new("bananas").unwrap().as_ref(), unsafe { - CStr::from_ptr(entry.name as *const c_char) - }); - } - - #[test] - fn nif1() { - let entry = get_entry!("nifs", [("raw1", 3, raw_nif1)]); - let funcs = unsafe { slice::from_raw_parts(entry.funcs, entry.num_of_funcs as usize) }; - assert_eq!(1, funcs.len()); - assert_eq!(CString::new("raw1").unwrap().as_ref(), unsafe { - CStr::from_ptr(funcs[0].name as *const c_char) - }); - assert_eq!(3, funcs[0].arity); - assert_eq!(28, unsafe { - (funcs[0].function)(ptr::null_mut(), 4, ptr::null_mut()) - }); - assert_eq!(0, funcs[0].flags); - } - - #[test] - fn nif2() { - let entry = get_entry!( - "nifs", - [ - ("raw1", 3, raw_nif1), - ("raw2", 33, raw_nif2, ERL_NIF_DIRTY_JOB_IO_BOUND) - ] - ); - let funcs = unsafe { slice::from_raw_parts(entry.funcs, entry.num_of_funcs as usize) }; - assert_eq!(2, funcs.len()); - assert_eq!(CString::new("raw1").unwrap().as_ref(), unsafe { - CStr::from_ptr(funcs[0].name as *const c_char) - }); - assert_eq!(3, funcs[0].arity); - assert_eq!(28, unsafe { - (funcs[0].function)(ptr::null_mut(), 4, ptr::null_mut()) - }); - assert_eq!(0, funcs[0].flags); - assert_eq!(CString::new("raw2").unwrap().as_ref(), unsafe { - CStr::from_ptr(funcs[1].name as *const c_char) - }); - assert_eq!(33, funcs[1].arity); - assert_eq!(44, unsafe { - (funcs[1].function)(ptr::null_mut(), 4, ptr::null_mut()) - }); - assert_eq!(ERL_NIF_DIRTY_JOB_IO_BOUND, funcs[1].flags); - } - - #[test] - fn nif_closure() { - let entry = get_entry!("nifs", [("closure", 5, |_, argc, _| (argc * 13) as usize)]); - let funcs = unsafe { slice::from_raw_parts(entry.funcs, entry.num_of_funcs as usize) }; - assert_eq!(1, funcs.len()); - assert_eq!(CString::new("closure").unwrap().as_ref(), unsafe { - CStr::from_ptr(funcs[0].name as *const c_char) - }); - assert_eq!(5, funcs[0].arity); - assert_eq!(52, unsafe { - (funcs[0].function)(ptr::null_mut(), 4, ptr::null_mut()) - }); - assert_eq!(0, funcs[0].flags); - } - - #[test] - fn nif_wrapped() { - let entry = get_entry!("nifs", [("sliced", 6, slice_args!(slice_nif))]); - let funcs = unsafe { slice::from_raw_parts(entry.funcs, entry.num_of_funcs as usize) }; - assert_eq!(1, funcs.len()); - assert_eq!(CString::new("sliced").unwrap().as_ref(), unsafe { - CStr::from_ptr(funcs[0].name as *const c_char) - }); - assert_eq!(6, funcs[0].arity); - assert_eq!(34, unsafe { - (funcs[0].function)(ptr::null_mut(), 2, ptr::null_mut()) - }); - assert_eq!(0, funcs[0].flags); - } - - #[test] - fn legacy() { - let entry = get_entry!( - b"legacymod\0", - Some(c_load), - None, - None, - Some(c_unload), - nif!(b"cnif_1\0", 7, c_nif1, ERL_NIF_DIRTY_JOB_IO_BOUND), - nif!(b"cnif_2\0", 8, c_nif1) - ); - let funcs = unsafe { slice::from_raw_parts(entry.funcs, entry.num_of_funcs as usize) }; - - assert_eq!(CString::new("legacymod").unwrap().as_ref(), unsafe { - CStr::from_ptr(entry.name as *const c_char) - }); - - assert_eq!(114, unsafe { - entry.load.unwrap()(ptr::null_mut(), ptr::null_mut(), 0) - }); - assert_eq!(None, entry.reload); - assert_eq!(None, entry.upgrade); - unsafe { entry.unload.unwrap()(ptr::null_mut(), ptr::null_mut()) }; // shouldn't panic or crash - - assert_eq!(2, funcs.len()); - - assert_eq!(CString::new("cnif_1").unwrap().as_ref(), unsafe { - CStr::from_ptr(funcs[0].name as *const c_char) - }); - assert_eq!(7, funcs[0].arity); - assert_eq!(38, unsafe { - (funcs[0].function)(ptr::null_mut(), 2, ptr::null_mut()) - }); - assert_eq!(ERL_NIF_DIRTY_JOB_IO_BOUND, funcs[0].flags); - - assert_eq!(CString::new("cnif_2").unwrap().as_ref(), unsafe { - CStr::from_ptr(funcs[1].name as *const c_char) - }); - assert_eq!(8, funcs[1].arity); - assert_eq!(57, unsafe { - (funcs[1].function)(ptr::null_mut(), 3, ptr::null_mut()) - }); - assert_eq!(0, funcs[1].flags); - } - - #[test] - fn trailing_comma() { - get_entry!("nifs", - [ - ("raw1", 3, raw_nif1), - ("raw2", 33, raw_nif2, ERL_NIF_DIRTY_JOB_IO_BOUND), // <- trailing comma - ], - { - unload: unload, - load: load, // <- trailing comma - }); - } - - #[test] - fn unsafe_callbacks() { - let entry = get_entry!("unsafe_nifs", [("unsafe_nif", 3, unsafe_nif)], { - load: unsafe_load - }); - let funcs = unsafe { slice::from_raw_parts(entry.funcs, entry.num_of_funcs as usize) }; - assert_eq!(15, unsafe { - entry.load.unwrap()(ptr::null_mut(), ptr::null_mut(), 0) - }); - assert_eq!(CString::new("unsafe_nif").unwrap().as_ref(), unsafe { - CStr::from_ptr(funcs[0].name as *const c_char) - }); - assert_eq!(3, funcs[0].arity); - assert_eq!(46, unsafe { - (funcs[0].function)(ptr::null_mut(), 2, ptr::null_mut()) - }); - assert_eq!(0, funcs[0].flags); - } -} diff --git a/rustler_sys/src/lib.rs b/rustler_sys/src/lib.rs index 2a097e80..2218ac46 100644 --- a/rustler_sys/src/lib.rs +++ b/rustler_sys/src/lib.rs @@ -1,132 +1,10 @@ /*! Low level Rust bindings to the [Erlang NIF API](http://www.erlang.org/doc/man/erl_nif.html). - -# NIF Crate - -A NIF module is built by creating a new crate that uses `erlang_nif-sys` as a dependency. -(more) - -# NIF Functions - -All NIF functions must have the following signature: - -``` -use rustler_sys::*; -# fn main(){} //0 -fn my_nif(env: *mut ErlNifEnv, - argc: c_int, - args: *const ERL_NIF_TERM) -> ERL_NIF_TERM { - // ... -# unsafe{enif_make_badarg(env)} -} - -``` - -# NIF Module Initialization - -## For the Impatient -``` -use rustler_sys::*; - -nif_init!("my_nif_module",[ - ("my_nif_fun1", 1, my_nif_fun1), - ("my_dirty_fun2", 1, my_dirty_fun2, ERL_NIF_DIRTY_JOB_CPU_BOUND) - ], - {load: my_load} -); -# fn main(){} //1 -# fn my_load(env: *mut ErlNifEnv, priv_data: *mut *mut c_void, load_info: ERL_NIF_TERM)-> c_int { 0 } -# fn my_nif_fun1(_: *mut ErlNifEnv,_: c_int,args: *const ERL_NIF_TERM) -> ERL_NIF_TERM {unsafe{*args}} -# fn my_dirty_fun2(_: *mut ErlNifEnv,_: c_int,args: *const ERL_NIF_TERM) -> ERL_NIF_TERM {unsafe{*args}} -``` - -## Details - -The `erlang_nif-sys` analog of [`ERL_NIF_INIT()`](http://www.erlang.org/doc/man/erl_nif_init.html) is `nif_init!` which has the following form: - -`nif_init!(module_name, [nif_funcs], {options})` - -`module_name` must be a string literal, for example `"mynifmodule"`. - - -`nif_funcs` declares all the exported NIF functions for this module. Each entry is declared as - -`(name, arity, function, flags)` - -`name` is a string literal indicating the name of the function as seen from Erlang code. -`arity` is an integer indicating how many parameter this function takes as seen from Erlang code. -`function` is the Rust implementation of the NIF and must be of the form -`Fn(env: *mut ErlNifEnv, argc: c_int, args: *const ERL_NIF_TERM) -> ERL_NIF_TERM`. This is usually a plain -Rust function, but closures are permitted. -`flags` is optional and allows you to specify if this NIF is to run on a dirty scheduler. See [dirty NIFs](http://www.erlang.org/doc/man/erl_nif.html#dirty_nifs) -in the Erlang docs. - -The `options` are the NIF module initialization functions [`load`](http://www.erlang.org/doc/man/erl_nif.html#load), [`reload`](http://www.erlang.org/doc/man/erl_nif.html#reload), -[`upgrade`](http://www.erlang.org/doc/man/erl_nif.html#upgrade), and [`unload`](http://www.erlang.org/doc/man/erl_nif.html#unload). -Each is optional and is specified in struct-init style if present. If no options are needed, -the curly braces may be elided. Stub implementation of all these functions looks something like: - -``` -use rustler_sys::*; - -nif_init!("mymod", [], {load: load, reload: reload, upgrade: upgrade, unload: unload}); - -fn load(env: *mut ErlNifEnv, - priv_data: *mut *mut c_void, - load_info: ERL_NIF_TERM)-> c_int { 0 } - -fn reload(env: *mut ErlNifEnv, - priv_data: *mut *mut c_void, - load_info: ERL_NIF_TERM) -> c_int { 0 } - -fn upgrade(env: *mut ErlNifEnv, - priv_data: *mut *mut c_void, - old_priv_data: *mut *mut c_void, - load_info: ERL_NIF_TERM) -> c_int { 0 } - -fn unload(env: *mut ErlNifEnv, - priv_data: *mut c_void) {} - -# fn main(){} //2 -``` - -# Invoking NIF API - -As with any Rust FFI call, NIF API calls must be wrapped in `unsafe` blocks. -Below is an example of invoking NIF APIs along with an approach for dealing with -the the `args` parameter. - -``` -use rustler_sys::*; -use std::mem; - -fn native_add(env: *mut ErlNifEnv, - argc: c_int, - args: *const ERL_NIF_TERM) -> ERL_NIF_TERM { - unsafe { - let mut a: c_int = mem::uninitialized(); - let mut b: c_int = mem::uninitialized(); - if argc == 2 && - 0 != enif_get_int(env, *args, &mut a) && - 0 != enif_get_int(env, *args.offset(1), &mut b) { - enif_make_int(env, a+b) - } - else { - enif_make_badarg(env) - } - } -} -# fn main(){} //3 -``` - */ // Don't throw warnings on NIF naming conventions #![allow(non_camel_case_types)] -#[macro_use] -mod initmacro; - pub mod rustler_sys_api; pub use crate::rustler_sys_api::*; From 0c23ef7bfdf3daace33113dfaada3cac84b31508 Mon Sep 17 00:00:00 2001 From: Philip Sampaio Date: Thu, 28 Dec 2023 23:24:41 -0300 Subject: [PATCH 18/28] Fix Term's "get_type()" implementation It was not returning the correct type for integers, probably due to a "garbage" arriving because of the wrong type. --- rustler/src/dynamic.rs | 2 +- rustler/src/term.rs | 2 +- rustler_sys/build.rs | 2 +- rustler_tests/lib/rustler_test.ex | 1 + rustler_tests/native/rustler_test/src/lib.rs | 1 + .../native/rustler_test/src/test_term.rs | 31 +++++++++++++++++++ rustler_tests/test/term_test.exs | 13 ++++++++ 7 files changed, 49 insertions(+), 3 deletions(-) diff --git a/rustler/src/dynamic.rs b/rustler/src/dynamic.rs index ad73f888..0a3996f0 100644 --- a/rustler/src/dynamic.rs +++ b/rustler/src/dynamic.rs @@ -43,7 +43,7 @@ impl From for TermType { } pub fn get_type(term: Term) -> TermType { - if cfg!(nif_version_2_15) { + if cfg!(feature = "nif_version_2_15") { term.get_erl_type().into() } else if term.is_atom() { TermType::Atom diff --git a/rustler/src/term.rs b/rustler/src/term.rs index 27bf5950..b0d81b4c 100644 --- a/rustler/src/term.rs +++ b/rustler/src/term.rs @@ -124,7 +124,7 @@ impl<'a> Term<'a> { #[cfg(feature = "nif_version_2_15")] pub fn get_erl_type(&self) -> rustler_sys::ErlNifTermType { - unsafe { rustler_sys::enif_term_type(self.env.as_c_arg(), &self.as_c_arg()) } + unsafe { rustler_sys::enif_term_type(self.env.as_c_arg(), self.as_c_arg()) } } } diff --git a/rustler_sys/build.rs b/rustler_sys/build.rs index f873b023..70bdfe81 100644 --- a/rustler_sys/build.rs +++ b/rustler_sys/build.rs @@ -848,7 +848,7 @@ fn build_api(b: &mut dyn ApiBuilder, opts: &GenerateOptions) { b.func( "ErlNifTermType", "enif_term_type", - "env: *mut ErlNifEnv, term: *const ERL_NIF_TERM", + "env: *mut ErlNifEnv, term: ERL_NIF_TERM", ); b.func("c_int", "enif_is_pid_undefined", "pid: *const ErlNifPid"); diff --git a/rustler_tests/lib/rustler_test.ex b/rustler_tests/lib/rustler_test.ex index 7de1d85a..1adaded7 100644 --- a/rustler_tests/lib/rustler_test.ex +++ b/rustler_tests/lib/rustler_test.ex @@ -47,6 +47,7 @@ defmodule RustlerTest do def term_cmp(_, _), do: err() def term_internal_hash(_, _), do: err() def term_phash2_hash(_), do: err() + def term_type(_term), do: err() def sum_map_values(_), do: err() def map_entries_sorted(_), do: err() diff --git a/rustler_tests/native/rustler_test/src/lib.rs b/rustler_tests/native/rustler_test/src/lib.rs index 6b0e4ece..efe2372e 100644 --- a/rustler_tests/native/rustler_test/src/lib.rs +++ b/rustler_tests/native/rustler_test/src/lib.rs @@ -30,6 +30,7 @@ rustler::init!( test_term::term_cmp, test_term::term_internal_hash, test_term::term_phash2_hash, + test_term::term_type, test_map::sum_map_values, test_map::map_entries_sorted, test_map::map_from_arrays, diff --git a/rustler_tests/native/rustler_test/src/test_term.rs b/rustler_tests/native/rustler_test/src/test_term.rs index a499d80f..1bafdbea 100644 --- a/rustler_tests/native/rustler_test/src/test_term.rs +++ b/rustler_tests/native/rustler_test/src/test_term.rs @@ -7,6 +7,19 @@ mod atoms { equal, less, greater, + // Term types + atom, + binary, + float, + fun, + integer, + list, + map, + pid, + port, + reference, + tuple, + unknown, } } @@ -40,3 +53,21 @@ pub fn term_internal_hash(term: Term, salt: u32) -> u32 { pub fn term_phash2_hash(term: Term) -> u32 { term.hash_phash2() } + +#[rustler::nif] +pub fn term_type(term: Term) -> Atom { + match term.get_type() { + rustler::TermType::Atom => atoms::atom(), + rustler::TermType::Binary => atoms::binary(), + rustler::TermType::Fun => atoms::fun(), + rustler::TermType::List => atoms::list(), + rustler::TermType::Map => atoms::map(), + rustler::TermType::Integer => atoms::integer(), + rustler::TermType::Float => atoms::float(), + rustler::TermType::Pid => atoms::pid(), + rustler::TermType::Port => atoms::port(), + rustler::TermType::Ref => atoms::reference(), + rustler::TermType::Tuple => atoms::tuple(), + rustler::TermType::Unknown => atoms::unknown(), + } +} diff --git a/rustler_tests/test/term_test.exs b/rustler_tests/test/term_test.exs index 99f663b4..3b59d3b9 100644 --- a/rustler_tests/test/term_test.exs +++ b/rustler_tests/test/term_test.exs @@ -67,4 +67,17 @@ defmodule RustlerTest.TermTest do assert unique > 50 end + + test "term type" do + assert RustlerTest.term_type(:foo) == :atom + assert RustlerTest.term_type("foo") == :binary + assert RustlerTest.term_type(42.2) == :float + assert RustlerTest.term_type(42) == :integer + assert RustlerTest.term_type(%{}) == :map + assert RustlerTest.term_type([]) == :list + assert RustlerTest.term_type({:ok, 42}) == :tuple + assert RustlerTest.term_type(self()) == :pid + assert RustlerTest.term_type(&Function.identity/1) == :fun + assert RustlerTest.term_type(make_ref()) == :reference + end end From 7932e54cd3c0b237fdbb3b3322a577698f7f30f4 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 6 Feb 2024 07:55:56 +0100 Subject: [PATCH 19/28] Deactivate new get_term codepath on Windows for now --- UPGRADE.md | 5 +++-- rustler/src/dynamic.rs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/UPGRADE.md b/UPGRADE.md index 393e23e2..43e6130c 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -19,8 +19,9 @@ This document is intended to simplify upgrading to newer versions by extending t See also [enif\_send](https://www.erlang.org/doc/man/erl_nif.html#enif_send). -3. As `Term::get_type` is now implemented using `enif_get_type`, some cases of - the `TermType` `enum` are changed, removed, or added: +3. As `Term::get_type` is now implemented using `enif_get_type` on all + non-Windows systems, some cases of the `TermType` `enum` are changed, + removed, or added: 1. `EmptyList` is dropped, `List` is returned for both empty and non-empty lists 2. `Exception` is dropped diff --git a/rustler/src/dynamic.rs b/rustler/src/dynamic.rs index 0a3996f0..5c5eb278 100644 --- a/rustler/src/dynamic.rs +++ b/rustler/src/dynamic.rs @@ -43,7 +43,7 @@ impl From for TermType { } pub fn get_type(term: Term) -> TermType { - if cfg!(feature = "nif_version_2_15") { + if cfg!(feature = "nif_version_2_15") && !cfg!(target_family = "windows") { term.get_erl_type().into() } else if term.is_atom() { TermType::Atom From 940423c86af937f8c15e1ce89101a04e19fa8aae Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 7 Feb 2024 07:56:17 +0100 Subject: [PATCH 20/28] Update UPGRADE documentation --- UPGRADE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UPGRADE.md b/UPGRADE.md index 43e6130c..8c4f5546 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -21,7 +21,7 @@ This document is intended to simplify upgrading to newer versions by extending t 3. As `Term::get_type` is now implemented using `enif_get_type` on all non-Windows systems, some cases of the `TermType` `enum` are changed, - removed, or added: + removed, or added (on all systems): 1. `EmptyList` is dropped, `List` is returned for both empty and non-empty lists 2. `Exception` is dropped From d79d9181bab7ea5b1d345d6c6a643f840ec47589 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 7 Feb 2024 07:47:41 +0100 Subject: [PATCH 21/28] Implement is_float using enif_get_double --- rustler/src/dynamic.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/rustler/src/dynamic.rs b/rustler/src/dynamic.rs index 5c5eb278..19b0a98c 100644 --- a/rustler/src/dynamic.rs +++ b/rustler/src/dynamic.rs @@ -1,3 +1,5 @@ +use std::ffi::c_double; + #[cfg(feature = "nif_version_2_15")] use rustler_sys::ErlNifTermType; @@ -56,7 +58,11 @@ pub fn get_type(term: Term) -> TermType { } else if term.is_map() { TermType::Map } else if term.is_number() { - TermType::Float + if term.is_float() { + TermType::Float + } else { + TermType::Integer + } } else if term.is_pid() { TermType::Pid } else if term.is_port() { @@ -98,4 +104,15 @@ impl<'a> Term<'a> { impl_check!(is_port); impl_check!(is_ref); impl_check!(is_tuple); + + pub fn is_float(self) -> bool { + let mut val: c_double = 0.0; + unsafe { + rustler_sys::enif_get_double(self.get_env().as_c_arg(), self.as_c_arg(), &mut val) == 1 + } + } + + pub fn is_integer(self) -> bool { + self.is_number() && !self.is_float() + } } From 63ec23c7653488746d0ad587cd49980a1cd9ed4f Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 10 Feb 2024 23:53:54 +0100 Subject: [PATCH 22/28] Use a different test object for funs OTP24 apparently does not correctly recognize remote funs (`fun mod:func/1`) in `is_fun`. --- rustler_tests/test/term_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rustler_tests/test/term_test.exs b/rustler_tests/test/term_test.exs index 3b59d3b9..35bbab9a 100644 --- a/rustler_tests/test/term_test.exs +++ b/rustler_tests/test/term_test.exs @@ -77,7 +77,7 @@ defmodule RustlerTest.TermTest do assert RustlerTest.term_type([]) == :list assert RustlerTest.term_type({:ok, 42}) == :tuple assert RustlerTest.term_type(self()) == :pid - assert RustlerTest.term_type(&Function.identity/1) == :fun + assert RustlerTest.term_type(& &1) == :fun assert RustlerTest.term_type(make_ref()) == :reference end end From bf1385a8561371ad654405563745bed0b720bd8a Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 13 Feb 2024 15:00:59 +0100 Subject: [PATCH 23/28] Use impl Encoder in more places to simplify function usage (#572) --- CHANGELOG.md | 6 ++++++ rustler/src/env.rs | 31 ++++++++++++++++++------------- rustler/src/term.rs | 2 +- rustler/src/thread.rs | 3 +-- rustler/src/types/list.rs | 3 ++- 5 files changed, 28 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0aa848d..e1e149bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 See [`UPGRADE.md`](./UPGRADE.md) for additional help when upgrading to newer versions. +## [unreleased] + +### Changed + +* Use `impl Encoder` on more functions (in particular on `send`) (#572) + ## [0.30.0] - 2023-10-11 ### Added diff --git a/rustler/src/env.rs b/rustler/src/env.rs index 88cad3eb..4492151e 100644 --- a/rustler/src/env.rs +++ b/rustler/src/env.rs @@ -56,10 +56,7 @@ impl<'a> Env<'a> { } /// Convenience method for building a tuple `{error, Reason}`. - pub fn error_tuple(self, reason: T) -> Term<'a> - where - T: Encoder, - { + pub fn error_tuple(self, reason: impl Encoder) -> Term<'a> { let error = crate::types::atom::error().to_term(self); (error, reason).encode(self) } @@ -83,7 +80,7 @@ impl<'a> Env<'a> { /// Panics if the above rules are broken (by trying to send a message from /// an `OwnedEnv` on a thread that's managed by the Erlang VM). /// - pub fn send(self, pid: &LocalPid, message: Term<'a>) -> Result<(), SendError> { + pub fn send(self, pid: &LocalPid, message: impl Encoder) -> Result<(), SendError> { let thread_type = unsafe { rustler_sys::enif_thread_type() }; let env = if thread_type == rustler_sys::ERL_NIF_THR_UNDEFINED { ptr::null_mut() @@ -99,6 +96,8 @@ impl<'a> Env<'a> { panic!("Env::send(): unrecognized calling thread type"); }; + let message = message.encode(self); + // Send the message. let res = unsafe { rustler_sys::enif_send(env, pid.as_c_arg(), ptr::null_mut(), message.as_c_arg()) @@ -120,7 +119,8 @@ impl<'a> Env<'a> { /// - `Some(pid)` if `name_or_pid` is an atom and an alive process is currently registered under the given name. /// - `None` if `name_or_pid` is an atom but there is no alive process registered under this name. /// - `None` if `name_or_pid` is not a PID or atom. - pub fn whereis_pid(&self, name_or_pid: Term<'a>) -> Option { + pub fn whereis_pid(self, name_or_pid: impl Encoder) -> Option { + let name_or_pid = name_or_pid.encode(self); if name_or_pid.is_pid() { return Some(name_or_pid.decode().unwrap()); } @@ -198,9 +198,9 @@ impl OwnedEnv { } /// Run some code in this environment. - pub fn run(&self, closure: F) -> R + pub fn run<'a, F, R>(&self, closure: F) -> R where - F: for<'a> FnOnce(Env<'a>) -> R, + F: FnOnce(Env<'a>) -> R, { let env = unsafe { Env::new(&(), *self.env) }; closure(env) @@ -219,15 +219,20 @@ impl OwnedEnv { /// can only use this method on a thread that was created by other /// means. (This curious restriction is imposed by the Erlang VM.) /// - pub fn send_and_clear(&mut self, recipient: &LocalPid, closure: F) -> Result<(), SendError> + pub fn send_and_clear<'a, F, T>( + &mut self, + recipient: &LocalPid, + closure: F, + ) -> Result<(), SendError> where - F: for<'a> FnOnce(Env<'a>) -> Term<'a>, + F: FnOnce(Env<'a>) -> T, + T: Encoder, { if unsafe { rustler_sys::enif_thread_type() } != rustler_sys::ERL_NIF_THR_UNDEFINED { panic!("send_and_clear: current thread is managed"); } - let message = self.run(|env| closure(env).as_c_arg()); + let message = self.run(|env| closure(env).encode(env).as_c_arg()); let res = unsafe { rustler_sys::enif_send(ptr::null_mut(), recipient.as_c_arg(), *self.env, message) @@ -287,9 +292,9 @@ impl OwnedEnv { /// /// **Note: There is no way to save terms across `OwnedEnv::send()` or `clear()`.** /// If you try, the `.load()` call will panic. - pub fn save(&self, term: Term) -> SavedTerm { + pub fn save(&self, term: impl Encoder) -> SavedTerm { SavedTerm { - term: self.run(|env| term.in_env(env).as_c_arg()), + term: self.run(|env| term.encode(env).as_c_arg()), env_generation: Arc::downgrade(&self.env), } } diff --git a/rustler/src/term.rs b/rustler/src/term.rs index b0d81b4c..02e8c1cc 100644 --- a/rustler/src/term.rs +++ b/rustler/src/term.rs @@ -37,7 +37,7 @@ impl<'a> Term<'a> { self.term } - pub fn get_env(&self) -> Env<'a> { + pub fn get_env(self) -> Env<'a> { self.env } diff --git a/rustler/src/thread.rs b/rustler/src/thread.rs index e189393a..0bc9c8b8 100644 --- a/rustler/src/thread.rs +++ b/rustler/src/thread.rs @@ -1,6 +1,5 @@ use crate::env::OwnedEnv; -use crate::types::atom::Atom; -use crate::{Encoder, Env, Term}; +use crate::{Atom, Encoder, Env, Term}; use std::panic; use std::thread; diff --git a/rustler/src/types/list.rs b/rustler/src/types/list.rs index 342d45f6..9404b841 100644 --- a/rustler/src/types/list.rs +++ b/rustler/src/types/list.rs @@ -193,8 +193,9 @@ impl<'a> Term<'a> { } /// Adds `head` in a list cell with `self` as tail. - pub fn list_prepend(self, head: Term<'a>) -> Term<'a> { + pub fn list_prepend(self, head: impl Encoder) -> Term<'a> { let env = self.get_env(); + let head = head.encode(env); unsafe { let term = list::make_list_cell(env.as_c_arg(), head.as_c_arg(), self.as_c_arg()); Term::new(env, term) From a9bb8feeaf6d2ce52a7c760a954e14737afe8843 Mon Sep 17 00:00:00 2001 From: Philip Sampaio Date: Tue, 13 Feb 2024 11:41:03 -0300 Subject: [PATCH 24/28] Fix atoms module name generated by the `NifStruct` derive macro (#585) This is related to https://github.com/rusterlium/rustler/pull/573, but now the macro is given a warning even with the annotation to ignore. So this is a fix that turns the atoms module name generated by the macro in a snake case name. --- rustler_codegen/src/context.rs | 5 ++++- rustler_codegen/src/ex_struct.rs | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/rustler_codegen/src/context.rs b/rustler_codegen/src/context.rs index 6988734c..077da964 100644 --- a/rustler_codegen/src/context.rs +++ b/rustler_codegen/src/context.rs @@ -86,7 +86,10 @@ impl<'a> Context<'a> { } pub fn atoms_module_name(&self, span: Span) -> Ident { - Ident::new(&format!("RUSTLER_ATOMS_{}", self.ident), span) + Ident::new( + &format!("rustler_atoms_{}", self.ident).to_snake_case(), + span, + ) } pub fn encode(&self) -> bool { diff --git a/rustler_codegen/src/ex_struct.rs b/rustler_codegen/src/ex_struct.rs index 4d720606..e051c494 100644 --- a/rustler_codegen/src/ex_struct.rs +++ b/rustler_codegen/src/ex_struct.rs @@ -53,7 +53,6 @@ pub fn transcoder_decorator(ast: &syn::DeriveInput, add_exception: bool) -> Toke }; let gen = quote! { - #[allow(non_snake_case)] mod #atoms_module_name { #atom_defs } From d1b155257b9cd61619435fc25ed9772162329b3b Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 13 Feb 2024 15:56:21 +0100 Subject: [PATCH 25/28] Update and fix changelog (#590) --- CHANGELOG.md | 356 ++++++++++++++++++++++++++++----------------------- 1 file changed, 196 insertions(+), 160 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1e149bd..8b9e4717 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,89 +2,111 @@ All notable changes to this project will be documented in this file. -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -See [`UPGRADE.md`](./UPGRADE.md) for additional help when upgrading to newer versions. +See [`UPGRADE.md`](./UPGRADE.md) for additional help when upgrading to newer +versions. ## [unreleased] +### Added + +- Support for generic types in derive macros (#574) +- New `is_float` and `is_integer` methods on terms (#581) + +### Fixed + +- Finalized making `:rustler` a compile-time-only dependency (#570) +- Make `get_type` work as documented for 0.30 (#581) +- Tests on ARM64 (#584) +- Error messages in codegen (#579) + ### Changed -* Use `impl Encoder` on more functions (in particular on `send`) (#572) +- Use `impl Encoder` on more functions (in particular on `send`) (#572) +- The generated atom modules for derived structs are now called + `rustler_atoms_{struct_name_in_snakecase}` to silence warnings (#585) + +### Removed + +- Support for `initmacro` in `rustler-sys` (will only be released on the next + bump of `rustler-sys` itself, #589) ## [0.30.0] - 2023-10-11 ### Added -* Return `Result<(), SendError>` from all `send` functions (#239, #563) +- Return `Result<(), SendError>` from all `send` functions (#239, #563) ### Changed -* Drop support for `RUSTLER_NIF_VERSION` -* Deprecate `:rustler_crates` project configuration -* Mark `use Rustler` module configuration as compile-time -* Bump Rust edition to 2021 -* Make `:rustler` a compile-time-only dependency (#516, #559) -* Use `enif_term_type` to implement `Term::get_type` (#538). Please check the +- Deprecate `:rustler_crates` project configuration +- Mark `use Rustler` module configuration as compile-time +- Bump Rust edition to 2021 +- Make `:rustler` a compile-time-only dependency (#516, #559) +- Use `enif_term_type` to implement `Term::get_type` (#538). Please check the `UPGRADE` documentation for necessary code changes. -* Raise default NIF version to 2.15 +- Raise default NIF version to 2.15 + +### Removed + +- Support for `RUSTLER_NIF_VERSION`, NIF version requirements have to be set via + features now ## [0.29.1] - 2023-06-30 ### Fixed -* Exclude directories from external resources for compatibility with Elixir 1.15 +- Exclude directories from external resources for compatibility with Elixir 1.15 (#548, thanks @adrienmo) -* Fix `NifTaggedEnum` derived `Encoder` impl for named-field variants (#547, +- Fix `NifTaggedEnum` derived `Encoder` impl for named-field variants (#547, thanks @dylanburati) -* Remove `cfg!` directives in build.rs causing cross-compilation to fail (#555, +- Remove `cfg!` directives in build.rs causing cross-compilation to fail (#555, thanks @fabriziosestito) ## [0.29.0] - 2023-06-22 ### Added -* `ErlOption` to provide an ergonomic option type for Erlang (#507, thanks @tatsuya6502) +- `ErlOption` to provide an ergonomic option type for Erlang (#507, thanks @tatsuya6502) ### Changed -* Use Cargo features to define the NIF version level (#537), deprecating +- Use Cargo features to define the NIF version level (#537), deprecating `RUSTLER_NIF_VERSION` ## [0.28.0] - 2023-04-24 ### Added -* Support OTP 26 (#526, thanks @philss) -* Support tuples in NIF macro (#520, #527, thanks @denumerate and @philss) -* Support for `load_data_fun` to compute `load_data` at runtime (#413, thanks @kaaboaye) +- Support OTP 26 (#526, thanks @philss) +- Support tuples in NIF macro (#520, #527, thanks @denumerate and @philss) +- Support for `load_data_fun` to compute `load_data` at runtime (#413, thanks + @kaaboaye) ### Changed -* Enhanced NIF macro error messages for invalid attributes (#525, thanks @philss) +- Enhanced NIF macro error messages for invalid attributes (#525, thanks @philss) ## [0.27.0] - 2023-01-17 -### BREAKING - -* `MIX_ENV` is no longer considered for determining the build profile. Now, the - profile defaults to `:release`. Use the `:mode` option to pick another - profile explicitly. (#496) - ### Added -* `ResourceArc::make_binary` for safe use of `enif_make_resource_binary` (#487) -* `OwnedBinary` is now `Sync` (#493) -* Specified MSRV to be 1.56.1. +- `ResourceArc::make_binary` for safe use of `enif_make_resource_binary` (#487) +- `OwnedBinary` is now `Sync` (#493) +- Specified MSRV to be 1.56.1. -### Fixed +### Changed -* Documentation for `load` (#501, thanks @ishitatsuyuki) +- `MIX_ENV` is no longer considered for determining the build profile. Now, the + profile defaults to `:release`. Use the `:mode` option to pick another + profile explicitly. (#496) +- Edition 2021 for the rustler mix template (#512, thanks @ayrat555) -### Changed +### Fixed -* Edition 2021 for the rustler mix template (#512, thanks @ayrat555) +- Documentation for `load` (#501, thanks @ishitatsuyuki) ## [0.26.0] - 2022-09-02 @@ -92,7 +114,8 @@ See [`UPGRADE.md`](./UPGRADE.md) for additional help when upgrading to newer ver #### TaggedEnum -We added `TaggedEnum`, which is a generalized enum type (#440, thanks to @SeokminHong!). Example: +We added `TaggedEnum`, which is a generalized enum type (#440, thanks to +@SeokminHong!). Example: ```rust #[derive(NifTaggedEnum)] @@ -107,71 +130,76 @@ pub enum TaggedEnum1 { On the Elixir side, the variants are represented as two-tuples `{tag::atom(), inner::term()} | atom()`, where the `inner` term is -* a map for the variant `Named` in the example above -* a binary for the `String1` and `String2` variants +- a map for the variant `Named` in the example above +- a binary for the `String1` and `String2` variants The `Untagged` variant is represented as the atom `:untagged` in Elixir. ### Added -* Added `Clone` and `Copy` for `TermType` (#476, thanks @dvic) -* Added `Env.whereis_pid()` (#456, thanks @Qqwy) +- Added `Clone` and `Copy` for `TermType` (#476, thanks @dvic) +- Added `Env.whereis_pid()` (#456, thanks @Qqwy) -### Improved +### Changed -* Use `&[impl Encoder]` for keys and values in `map_from_arrays()` to improve ergonomics (#453, thanks @SeokminHong) -* Improved encode/decode performance of TaggedEnum considerably (#482, thanks @cleaton) -* Test on OTP 25 (#455) +- Use `&[impl Encoder]` for keys and values in `map_from_arrays()` to improve + ergonomics (#453, thanks @SeokminHong) +- Improved encode/decode performance of TaggedEnum considerably (#482, thanks + @cleaton) +- Test on OTP 25 (#455) ### Fixed -* Lifetime handling in `rustler_codegen` (#483, thanks @turion @SeokminHong and @neosimsim) -* Support multiple variants with same field names in TaggedEnum (#482, thanks @cleaton) -* Support .toml file extension for cargo config (#468, thanks @joshuataylor for the report in #467) -* Disambiguate `encode`/`decode` in generated code (#466, thanks @SeokminHong) -* Migrate CI to `erlef/setup-beam` (#457, thanks @SeokminHong) -* Documentation of the `schedule` flag for `nif` macro (#444) -* Improve documentation (#429, thanks @turion) +- Lifetime handling in `rustler_codegen` (#483, thanks @turion @SeokminHong and + @neosimsim) +- Support multiple variants with same field names in TaggedEnum (#482, thanks + @cleaton) +- Support .toml file extension for cargo config (#468, thanks @joshuataylor for + the report in #467) +- Disambiguate `encode`/`decode` in generated code (#466, thanks @SeokminHong) +- Migrate CI to `erlef/setup-beam` (#457, thanks @SeokminHong) +- Documentation of the `schedule` flag for `nif` macro (#444) +- Improve documentation (#429, thanks @turion) ## [0.25.0] - 2022-04-11 ### Added -* `NewBinary` now also available as `rustler::NewBinary` (thanks @ayrat555) -* `Term::map_from_pairs()` to conveniently build a map from a list -of pairs (thanks @philss) -* CI now also tests against macos +- `NewBinary` now also available as `rustler::NewBinary` (thanks @ayrat555) +- `Term::map_from_pairs()` to conveniently build a map from a list of pairs + (thanks @philss) +- CI now also tests against macos ### Fixed -* Snake-case warening for auto-generated `RUSTLER_{}_field_{}` variables (renamed to `rustler_{}_field_{}`) +- Snake-case warening for auto-generated `RUSTLER_{}_field_{}` variables + (renamed to `rustler_{}_field_{}`) ### Changed -* Abort compilation on macos if macos target configuration is missing +- Abort compilation on macos if macos target configuration is missing ## [0.24.0] - 2022-02-24 ### Added -* A `NewBinary` type to create binaries in Rust without going through +- A `NewBinary` type to create binaries in Rust without going through `OwnedBinary`. This can improve performance. Thanks @dlesl! -* `TermType` derives `Eq` and `PartialEq`. +- `TermType` derives `Eq` and `PartialEq`. -### Updated +### Fixed -* `rustler_mix`: Bumped required toml dependency to 0.6 -* Bumped `rustler_sys` dependency to `~2.2`. +- Set library file extension based on the compile target, thanks @cocoa-xu! +- Relaxed Jason version requirement to ~> 1.0 +- Various typos in the documentation, thanks @kianmeng! ### Changed -* Rustler supports the latest 3 versions of Elixir and OTP. Currently, those - are Elixir => 1.11 and OTP >= 22. - -### Fixed +- Rustler supports the latest 3 versions of Elixir and OTP. Currently, those are + Elixir => 1.11 and OTP >= 22. +- `rustler_mix`: Bumped required toml dependency to 0.6 +- Bumped `rustler_sys` dependency to `~2.2` -* Set library file extension based on the compile target, thanks @cocoa-xu! -* Relaxed Jason version requirement to ~> 1.0 -* Various typos in the documentation, thanks @kianmeng! ## [0.23.0] - 2021-12-22 @@ -181,24 +209,28 @@ The `Untagged` variant is represented as the atom `:untagged` in Elixir. - Hashing for term - Hash and Equality for `Binary` and `OwnedBinary` -### Changed - -- Rustler changed its supported range of OTP and Elixir versions. We aim to support the three newest versions of OTP and Elixir. -- The decoder for `Range` requires that `:step` equals `1`. The `:step` field was introduced with - Elixir v1.12 and cannot be represented with Rust's `RangeInclusive`. -- NIF API bindings are generated using Rust - -## Fixed +### Fixed - `mix rustler.new` with Elixir v1.13 - Template config for `macos` - Crash if metadata cannot be retrieved while compiling (#398) +### Changed + +- Rustler changed its supported range of OTP and Elixir versions. We aim to + support the three newest versions of OTP and Elixir. +- The decoder for `Range` requires that `:step` equals `1`. The `:step` field + was introduced with Elixir v1.12 and cannot be represented with Rust's + `RangeInclusive`. +- NIF API bindings are generated using Rust + ## [0.22.2] - 2021-10-07 ### Fixed -- Fixed a regression introduced with #386: `Rustler.Compiler.Config` called into `cargo` when `skip_compilation?` was set, breaking setups where cargo is not installed. Fixed with #389, thanks @karolsluszniak +- Fixed a regression introduced with #386: `Rustler.Compiler.Config` called into + `cargo` when `skip_compilation?` was set, breaking setups where cargo is not + installed. Fixed with #389, thanks @karolsluszniak ## [0.22.1] - 2021-10-05 @@ -215,7 +247,8 @@ The `Untagged` variant is represented as the atom `:untagged` in Elixir. - Simple `Debug` impl for `rustler::Error` - Support newtype and tuple structs for `NifTuple` and `NifRecord` -- `rustler::Error::Term` encoding an arbitrary boxed encoder, returning `{:error, term}` +- `rustler::Error::Term` encoding an arbitrary boxed encoder, returning + `{:error, term}` - Generic encoder/decoder for `HashMap`, where `T: Decoder` and `U: Decoder` ### Fixed @@ -223,7 +256,7 @@ The `Untagged` variant is represented as the atom `:untagged` in Elixir. - Compilation time of generated decoders has been reduced significantly. - Fixed a segfault caused by `OwnedEnv::send_and_clear` -### Changes +### Changed - Renamed `Pid` to `LocalPid` to clarify that it can't point to a remote process - Dependencies have been updated. @@ -233,97 +266,96 @@ The `Untagged` variant is represented as the atom `:untagged` in Elixir. - `rustler_atoms!` is now `rustler::atoms!` - `resource_struct_init!` is now `rustler::resource!` - New `rustler::atoms!` macro removed the `atom` prefix from the name: - -```rust -// -// Before -// -rustler::rustler_atoms! { - atom ok; - atom error; - atom renamed_atom = "Renamed"; -} - -// -// After -// -rustler::atoms! { - ok, - error, - renamed_atom = "Renamed", -} -``` + ```rust + // + // Before + // + rustler::rustler_atoms! { + atom ok; + atom error; + atom renamed_atom = "Renamed"; + } + + // + // After + // + rustler::atoms! { + ok, + error, + renamed_atom = "Renamed", + } + ``` - NIF functions can be initialized with a simplified syntax: + ```rust + // + // Before + // + rustler::rustler_export_nifs! { + "Elixir.Math", + [ + ("add", 2, add) + ], + None + } + + // + // After + // + rustler::init!("Elixir.Math", [add]); + ``` -```rust -// -// Before -// -rustler::rustler_export_nifs! { - "Elixir.Math", - [ - ("add", 2, add) - ], - None -} - -// -// After -// -rustler::init!("Elixir.Math", [add]); -``` - -- NIFs can be derived from regular functions, if the arguments implement `Decoder` and the return type implements `Encoder`: - -```rust -// -// Before -// -fn add<'a>(env: Env<'a>, args: &[Term<'a>]) -> Result, Error> { - let num1: i64 = args[0].decode()?; - let num2: i64 = args[1].decode()?; - - Ok((atoms::ok(), num1 + num2).encode(env)) -} - -// -// After -// -#[rustler::nif] -fn add(a: i64, b: i64) -> i64 { - a + b -} -``` - -- `rustler::nif` exposes more options to configure a NIF were the NIF is defined: +- NIFs can be derived from regular functions, if the arguments implement + `Decoder` and the return type implements `Encoder`: + ```rust + // + // Before + // + fn add<'a>(env: Env<'a>, args: &[Term<'a>]) -> Result, Error> { + let num1: i64 = args[0].decode()?; + let num2: i64 = args[1].decode()?; + + Ok((atoms::ok(), num1 + num2).encode(env)) + } + + // + // After + // + #[rustler::nif] + fn add(a: i64, b: i64) -> i64 { + a + b + } + ``` -```rust +- `rustler::nif` exposes more options to configure a NIF were the NIF is + defined: + ```rust -#[rustler::nif(schedule = "DirtyCpu")] -pub fn dirty_cpu() -> Atom { - let duration = Duration::from_millis(100); - std::thread::sleep(duration); + #[rustler::nif(schedule = "DirtyCpu")] + pub fn dirty_cpu() -> Atom { + let duration = Duration::from_millis(100); + std::thread::sleep(duration); - atoms::ok() -} + atoms::ok() + } -#[rustler::nif(name = "my_add")] -fn add(a: i64, b: i64) -> i64 { - a + b -} -``` + #[rustler::nif(name = "my_add")] + fn add(a: i64, b: i64) -> i64 { + a + b + } + ``` -### Deprecations +### Deprecated The rustler compiler has been deprecated and will be removed with v1.0. NIFs are no longer defined in `mix.exs`, but are configured with `use Rustler`. See the documentation for the `Rustler` module. To migrate to the new configuration: -* Drop `:rustler` from the `:compilers` key in your `mix.exs` `project/0` function -* Drop `:rustler_crates` from `project/0` and move the configurations into the `use Rustler` - of your NIF module or application config: +- Drop `:rustler` from the `:compilers` key in your `mix.exs` `project/0` + function +- Drop `:rustler_crates` from `project/0` and move the configurations into the + `use Rustler` of your NIF module or application config: ```elixir # config/dev.exs @@ -331,7 +363,8 @@ configuration: mode: :debug ``` -For more information, see [the documentation](https://hexdocs.pm/rustler/0.22.0-rc.1/Rustler.html#module-configuration-options). +For more information, see [the +documentation](https://hexdocs.pm/rustler/0.22.0-rc.1/Rustler.html#module-configuration-options). ## [0.21.0] - 2019-09-07 @@ -341,11 +374,14 @@ For more information, see [the documentation](https://hexdocs.pm/rustler/0.22.0- - Rust linting with [clippy](https://github.com/rust-lang/rust-clippy). - Support for decoding IOLists as binaries, `Term::decode_as_binary`. -### Changes +### Changed -- `rustler_codegen` is now reexported by the `rustler` crate. Depending on the `rustler_codegen` crate is deprecated. -- `erlang_nif-sys` has been renamed to `rustler_sys` and vendored into the rustler repo. +- `rustler_codegen` is now reexported by the `rustler` crate. Depending on the + `rustler_codegen` crate is deprecated. +- `erlang_nif-sys` has been renamed to `rustler_sys` and vendored into the + rustler repo. - Replaced the hand-rolled TOML parser in `rustler_mix` with the `toml-elixir` package. - Improve error messages for derived encoders/decoders. -- Rust `bool` now corresponds only to booleans (`false`, `true`) in Elixir. Previously, `nil` and `false` were both decodable to - `bool`. To use the previous behaviour, a `Truthy` newtype was introduced. +- Rust `bool` now corresponds only to booleans (`false`, `true`) in Elixir. + Previously, `nil` and `false` were both decodable to `bool`. To use the + previous behaviour, a `Truthy` newtype was introduced. From 0bcd00b50ce563a860e34592b37af84617f829e3 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 13 Feb 2024 15:58:34 +0100 Subject: [PATCH 26/28] (release) 0.31.0 --- rustler/Cargo.toml | 4 ++-- rustler_bigint/Cargo.toml | 2 +- rustler_codegen/Cargo.toml | 2 +- rustler_mix/README.md | 2 +- rustler_mix/lib/rustler.ex | 2 +- rustler_mix/mix.exs | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/rustler/Cargo.toml b/rustler/Cargo.toml index e5c53a99..343d0664 100644 --- a/rustler/Cargo.toml +++ b/rustler/Cargo.toml @@ -2,7 +2,7 @@ name = "rustler" description = "Safe Rust wrappers for creating Erlang NIF functions" repository = "https://github.com/rusterlium/rustler" -version = "0.30.0" # rustler version +version = "0.31.0" # rustler version authors = ["Hansihe "] license = "MIT/Apache-2.0" readme = "../README.md" @@ -19,7 +19,7 @@ nif_version_2_17 = ["nif_version_2_16", "rustler_sys/nif_version_2_17"] [dependencies] lazy_static = "1.4" -rustler_codegen = { path = "../rustler_codegen", version = "0.30.0", optional = true} +rustler_codegen = { path = "../rustler_codegen", version = "0.31.0", optional = true} rustler_sys = { path = "../rustler_sys", version = "~2.3.0" } [package.metadata.release] diff --git a/rustler_bigint/Cargo.toml b/rustler_bigint/Cargo.toml index 87664709..fff56948 100644 --- a/rustler_bigint/Cargo.toml +++ b/rustler_bigint/Cargo.toml @@ -11,4 +11,4 @@ keywords = ["bigint", "Erlang", "Elixir"] [dependencies] num-bigint = {version = "0.4"} -rustler = {path = "../rustler", version = "0.30.0"} +rustler = {path = "../rustler", version = "0.31.0"} diff --git a/rustler_codegen/Cargo.toml b/rustler_codegen/Cargo.toml index 69db7d66..412432d0 100644 --- a/rustler_codegen/Cargo.toml +++ b/rustler_codegen/Cargo.toml @@ -2,7 +2,7 @@ name = "rustler_codegen" description = "Compiler plugin for Rustler" repository = "https://github.com/rusterlium/rustler/tree/master/rustler_codegen" -version = "0.30.0" # rustler_codegen version +version = "0.31.0" # rustler_codegen version authors = ["Hansihe "] license = "MIT/Apache-2.0" readme = "../README.md" diff --git a/rustler_mix/README.md b/rustler_mix/README.md index 3a78db7a..8def5646 100644 --- a/rustler_mix/README.md +++ b/rustler_mix/README.md @@ -15,7 +15,7 @@ This package is available on [Hex.pm](https://hex.pm/packages/rustler). To insta ```elixir def deps do [ - {:rustler, "~> 0.30.0", runtime: false} + {:rustler, "~> 0.31.0", runtime: false} ] end ``` diff --git a/rustler_mix/lib/rustler.ex b/rustler_mix/lib/rustler.ex index 226ff087..60b16d55 100644 --- a/rustler_mix/lib/rustler.ex +++ b/rustler_mix/lib/rustler.ex @@ -170,7 +170,7 @@ defmodule Rustler do end @doc false - def rustler_version, do: "0.30.0" + def rustler_version, do: "0.31.0" @doc """ Supported NIF API versions. diff --git a/rustler_mix/mix.exs b/rustler_mix/mix.exs index 980f5d6b..aea65760 100644 --- a/rustler_mix/mix.exs +++ b/rustler_mix/mix.exs @@ -2,7 +2,7 @@ defmodule Rustler.Mixfile do use Mix.Project @source_url "https://github.com/rusterlium/rustler" - @version "0.30.0" + @version "0.31.0" def project do [ From 9775e826af8ee95ebff38a0915605163401f96a8 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 13 Feb 2024 16:02:00 +0100 Subject: [PATCH 27/28] Bump rustler_sys version --- rustler/Cargo.toml | 2 +- rustler_sys/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rustler/Cargo.toml b/rustler/Cargo.toml index 343d0664..6e0b6cac 100644 --- a/rustler/Cargo.toml +++ b/rustler/Cargo.toml @@ -20,7 +20,7 @@ nif_version_2_17 = ["nif_version_2_16", "rustler_sys/nif_version_2_17"] [dependencies] lazy_static = "1.4" rustler_codegen = { path = "../rustler_codegen", version = "0.31.0", optional = true} -rustler_sys = { path = "../rustler_sys", version = "~2.3.0" } +rustler_sys = { path = "../rustler_sys", version = "~2.3.2" } [package.metadata.release] diff --git a/rustler_sys/Cargo.toml b/rustler_sys/Cargo.toml index 85cc6f65..788c9756 100644 --- a/rustler_sys/Cargo.toml +++ b/rustler_sys/Cargo.toml @@ -20,7 +20,7 @@ name = "rustler_sys" # When depending on this crate, you should ALWAYS # use a tilde requirements with AT LEAST `~MAJOR.MINOR`. # Example: "~2.0" -version = "2.3.1" +version = "2.3.2" authors = ["Daniel Goertzen "] description = "Create Erlang NIF modules in Rust using the C NIF API." From 3dd242ab4bce1c34c5e38e17c9f4b7990d4307b5 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 13 Feb 2024 16:06:16 +0100 Subject: [PATCH 28/28] Fix changelog --- CHANGELOG.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b9e4717..34a89324 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,16 @@ versions. ### Added +### Fixed + +### Changed + +### Removed + +## [0.31.0] - 2024-02-13 + +### Added + - Support for generic types in derive macros (#574) - New `is_float` and `is_integer` methods on terms (#581) @@ -30,8 +40,7 @@ versions. ### Removed -- Support for `initmacro` in `rustler-sys` (will only be released on the next - bump of `rustler-sys` itself, #589) +- Support for `initmacro` in `rustler-sys` (v2.3.2, #589) ## [0.30.0] - 2023-10-11