From 662c321ac966253c4ea1343c2cd1b098333dad7c Mon Sep 17 00:00:00 2001 From: Alexis Brodeur Date: Wed, 13 Aug 2025 15:36:50 -0400 Subject: [PATCH 1/4] Rework fine::Ok and fine::Error This rework ensures that `fine::Ok` and `fine::Error` can correctly convert when used in conjunction with container types like `std::variant`. The downside to this approach, as implemented, is the need to go through 2 factory functions, `fine::ok` and `fine::error`, to construct these. --- README.md | 23 +++++++++---- c_include/fine.hpp | 71 +++++++++++++++++++++++++++++++++------ test/c_src/finest.cpp | 42 ++++++++++++++++++++--- test/lib/finest/nif.ex | 5 +++ test/test/finest_test.exs | 8 +++++ 5 files changed, 128 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 6574678..9834a72 100644 --- a/README.md +++ b/README.md @@ -470,19 +470,28 @@ When it comes to NIFs, errors often indicate unexpected failures and raising an exception makes sense, however you may also want to handle certain errors gracefully by returning `:ok`/`:error` tuples, similarly to usual Elixir functions. Fine provides `Ok` and `Error` -types for this purpose. +types for this purpose, along with their respective factories `fine::ok` and +`fine::error`: ```c++ -fine::Ok<>() +fine::Ok<> example() { + return fine::ok(); +} // :ok -fine::Ok(1) +fine::Ok example() { + return fine::ok(1); // implicit conversion +} // {:ok, 1} -fine::Error<>() +fine::Error<> example() { + return fine::error(); +} // :error -fine::Error("something went wrong") +fine::Error example() { + return fine::error("something went wrong"); // implicit conversion +} // {:error, "something went wrong"} ``` @@ -492,10 +501,10 @@ a NIF may return: ```c++ std::variant, fine::Error> find_meaning(ErlNifEnv *env) { if (...) { - return fine::Error("something went wrong"); + return fine::error("something went wrong"); } - return fine::Ok(42); + return fine::ok(42); } ``` diff --git a/c_include/fine.hpp b/c_include/fine.hpp index 15d0384..39eb1b2 100644 --- a/c_include/fine.hpp +++ b/c_include/fine.hpp @@ -142,28 +142,79 @@ class Term { ERL_NIF_TERM term; }; +namespace __private__ { +template struct UnwrapRefwrapper { + using type = T; +}; + +template struct UnwrapRefwrapper> { + using type = T &; +}; + +template +using UnwrapDecayType = + typename UnwrapRefwrapper::type>::type; +} // namespace __private__ + // Represents a `:ok` tagged tuple, useful as a NIF result. template class Ok { public: - Ok(const Args &...items) : items(items...) {} + using Items = std::tuple; -private: - friend struct Encoder>; + Ok(Items &&items) : m_items(std::forward(items)) {} + + template + Ok(const Ok &other) : m_items{other.items()} {} + + template + Ok(Ok &&other) : m_items{other.items()} {} + + const Items &items() const & noexcept { return m_items; } + + Items &items() & noexcept { return m_items; } + + Items &&items() && noexcept { return m_items; } - std::tuple items; +private: + std::tuple m_items; }; // Represents a `:error` tagged tuple, useful as a NIF result. template class Error { public: - Error(const Args &...items) : items(items...) {} + using Items = std::tuple; -private: - friend struct Encoder>; + Error(Items &&items) : m_items(std::forward(items)) {} + + template + Error(const Error &other) : m_items{other.items()} {} + + template + Error(Error &&other) : m_items{other.items()} {} + + const Items &items() const & noexcept { return m_items; } + + Items &items() & noexcept { return m_items; } - std::tuple items; + Items &&items() && noexcept { return m_items; } + +private: + std::tuple m_items; }; +template +inline static Ok<__private__::UnwrapDecayType...> ok(Args &&...args) { + return Ok<__private__::UnwrapDecayType...>{ + std::make_tuple(std::forward(args)...)}; +} + +template +inline static Error<__private__::UnwrapDecayType...> +error(Args &&...args) { + return Error<__private__::UnwrapDecayType...>{ + std::make_tuple(std::forward(args)...)}; +} + namespace __private__ { template struct ResourceWrapper { T resource; @@ -895,7 +946,7 @@ template struct Encoder> { auto tag = __private__::atoms::ok; if constexpr (sizeof...(Args) > 0) { - return fine::encode(env, std::tuple_cat(std::tuple(tag), ok.items)); + return fine::encode(env, std::tuple_cat(std::tuple(tag), ok.items())); } else { return fine::encode(env, tag); } @@ -907,7 +958,7 @@ template struct Encoder> { auto tag = __private__::atoms::error; if constexpr (sizeof...(Args) > 0) { - return fine::encode(env, std::tuple_cat(std::tuple(tag), error.items)); + return fine::encode(env, std::tuple_cat(std::tuple(tag), error.items())); } else { return fine::encode(env, tag); } diff --git a/test/c_src/finest.cpp b/test/c_src/finest.cpp index ddb3cd2..14c8b9e 100644 --- a/test/c_src/finest.cpp +++ b/test/c_src/finest.cpp @@ -76,6 +76,9 @@ struct ExError { static constexpr auto is_exception = true; }; +template +using Result = std::variant, fine::Error>; + int64_t add(ErlNifEnv *, int64_t x, int64_t y) { return x + y; } FINE_NIF(add, 0); @@ -139,6 +142,37 @@ codec_tuple_int64_and_string(ErlNifEnv *, } FINE_NIF(codec_tuple_int64_and_string, 0); +Result codec_result_string_int64_ok(ErlNifEnv *, + std::string term) { + return fine::ok(term); +} +FINE_NIF(codec_result_string_int64_ok, 0); + +Result codec_result_string_int64_error(ErlNifEnv *, + int64_t term) { + return fine::error(term); +} +FINE_NIF(codec_result_string_int64_error, 0); + +Result +codec_result_string_int64_ok_conversion(ErlNifEnv *) { + return fine::ok("fine"); +} +FINE_NIF(codec_result_string_int64_ok_conversion, 0); + +Result +codec_result_string_int64_error_conversion(ErlNifEnv *) { + uint16_t result = 42; + return fine::error(result); +} +FINE_NIF(codec_result_string_int64_error_conversion, 0); + +std::variant, fine::Error<>> +codec_result_int64_string_void_ok_conversion(ErlNifEnv *) { + return fine::ok(static_cast(201702), "c++17"); +} +FINE_NIF(codec_result_int64_string_void_ok_conversion, 0); + std::vector codec_vector_int64(ErlNifEnv *, std::vector term) { return term; @@ -163,19 +197,19 @@ FINE_NIF(codec_struct, 0); ExError codec_struct_exception(ErlNifEnv *, ExError term) { return term; } FINE_NIF(codec_struct_exception, 0); -fine::Ok<> codec_ok_empty(ErlNifEnv *) { return fine::Ok(); } +fine::Ok<> codec_ok_empty(ErlNifEnv *) { return fine::ok(); } FINE_NIF(codec_ok_empty, 0); fine::Ok codec_ok_int64(ErlNifEnv *, int64_t term) { - return fine::Ok(term); + return fine::ok(term); } FINE_NIF(codec_ok_int64, 0); -fine::Error<> codec_error_empty(ErlNifEnv *) { return fine::Error(); } +fine::Error<> codec_error_empty(ErlNifEnv *) { return fine::error(); } FINE_NIF(codec_error_empty, 0); fine::Error codec_error_string(ErlNifEnv *, std::string term) { - return fine::Error(term); + return fine::error(term); } FINE_NIF(codec_error_string, 0); diff --git a/test/lib/finest/nif.ex b/test/lib/finest/nif.ex index 371ade0..ea42991 100644 --- a/test/lib/finest/nif.ex +++ b/test/lib/finest/nif.ex @@ -28,6 +28,11 @@ defmodule Finest.NIF do def codec_optional_int64(_term), do: err!() def codec_variant_int64_or_string(_term), do: err!() def codec_tuple_int64_and_string(_term), do: err!() + def codec_result_string_int64_ok(_term), do: err!() + def codec_result_string_int64_error(_term), do: err!() + def codec_result_string_int64_ok_conversion(), do: err!() + def codec_result_string_int64_error_conversion(), do: err!() + def codec_result_int64_string_void_ok_conversion(), do: err!() def codec_vector_int64(_term), do: err!() def codec_map_atom_int64(_term), do: err!() def codec_resource(_term), do: err!() diff --git a/test/test/finest_test.exs b/test/test/finest_test.exs index dc3bc9b..6c7e052 100644 --- a/test/test/finest_test.exs +++ b/test/test/finest_test.exs @@ -217,6 +217,14 @@ defmodule FinestTest do assert NIF.codec_error_empty() == :error assert NIF.codec_error_string("this is the reason") == {:error, "this is the reason"} end + + test "result" do + assert NIF.codec_result_string_int64_ok("fine") == {:ok, "fine"} + assert NIF.codec_result_string_int64_error(42) == {:error, 42} + assert NIF.codec_result_string_int64_ok_conversion() == {:ok, "fine"} + assert NIF.codec_result_string_int64_error_conversion() == {:error, 42} + assert NIF.codec_result_int64_string_void_ok_conversion() == {:ok, 201_702, "c++17"} + end end describe "resource" do From 016a7d2578edffd47b0ba16ee11df4ad7808d89a Mon Sep 17 00:00:00 2001 From: Alexis Brodeur Date: Wed, 13 Aug 2025 18:36:23 -0400 Subject: [PATCH 2/4] Allow CTAD for `fine::Ok` and `fine::Error` Class Template Argument Deduction allows class templates to be inferred from constructor arguments, which removes the need for `fine::ok` and `fine::error` as special factory functions. --- README.md | 19 ++++++++--------- c_include/fine.hpp | 49 +++++++++++++------------------------------ test/c_src/finest.cpp | 18 ++++++++-------- 3 files changed, 32 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 9834a72..9e0fdde 100644 --- a/README.md +++ b/README.md @@ -470,27 +470,26 @@ When it comes to NIFs, errors often indicate unexpected failures and raising an exception makes sense, however you may also want to handle certain errors gracefully by returning `:ok`/`:error` tuples, similarly to usual Elixir functions. Fine provides `Ok` and `Error` -types for this purpose, along with their respective factories `fine::ok` and -`fine::error`: +types for this purpose. ```c++ fine::Ok<> example() { - return fine::ok(); + return fine::Ok(); } // :ok fine::Ok example() { - return fine::ok(1); // implicit conversion + return fine::Ok(1); } // {:ok, 1} fine::Error<> example() { - return fine::error(); + return fine::Error(); } // :error fine::Error example() { - return fine::error("something went wrong"); // implicit conversion + return fine::Error("something went wrong"); } // {:error, "something went wrong"} ``` @@ -499,12 +498,12 @@ You can use `std::variant` to express a union of possible result types a NIF may return: ```c++ -std::variant, fine::Error> find_meaning(ErlNifEnv *env) { - if (...) { - return fine::error("something went wrong"); +std::variant, fine::Error> divmod(ErlNifEnv *env, int64_t a, int64_t b) { + if (b == 0) { + return fine::Error("division by zero"); } - return fine::ok(42); + return fine::Ok(a / b, a % b); } ``` diff --git a/c_include/fine.hpp b/c_include/fine.hpp index 39eb1b2..cf3ef3e 100644 --- a/c_include/fine.hpp +++ b/c_include/fine.hpp @@ -142,38 +142,27 @@ class Term { ERL_NIF_TERM term; }; -namespace __private__ { -template struct UnwrapRefwrapper { - using type = T; -}; - -template struct UnwrapRefwrapper> { - using type = T &; -}; - -template -using UnwrapDecayType = - typename UnwrapRefwrapper::type>::type; -} // namespace __private__ - // Represents a `:ok` tagged tuple, useful as a NIF result. template class Ok { public: using Items = std::tuple; - Ok(Items &&items) : m_items(std::forward(items)) {} + template >> + Ok() : m_items() {} + + explicit Ok(Args... items) : m_items{std::move(items)...} {} template - Ok(const Ok &other) : m_items{other.items()} {} + Ok(const Ok &other) : m_items(other.items()) {} template - Ok(Ok &&other) : m_items{other.items()} {} + Ok(Ok &&other) : m_items(std::move(other).items()) {} const Items &items() const & noexcept { return m_items; } Items &items() & noexcept { return m_items; } - Items &&items() && noexcept { return m_items; } + Items &&items() && noexcept { return std::move(m_items); } private: std::tuple m_items; @@ -184,37 +173,27 @@ template class Error { public: using Items = std::tuple; - Error(Items &&items) : m_items(std::forward(items)) {} + template >> + Error() : m_items() {} + + explicit Error(Args... items) : m_items{std::move(items)...} {} template - Error(const Error &other) : m_items{other.items()} {} + Error(const Error &other) : m_items(other.items()) {} template - Error(Error &&other) : m_items{other.items()} {} + Error(Error &&other) : m_items(std::move(other).items()) {} const Items &items() const & noexcept { return m_items; } Items &items() & noexcept { return m_items; } - Items &&items() && noexcept { return m_items; } + Items &&items() && noexcept { return std::move(m_items); } private: std::tuple m_items; }; -template -inline static Ok<__private__::UnwrapDecayType...> ok(Args &&...args) { - return Ok<__private__::UnwrapDecayType...>{ - std::make_tuple(std::forward(args)...)}; -} - -template -inline static Error<__private__::UnwrapDecayType...> -error(Args &&...args) { - return Error<__private__::UnwrapDecayType...>{ - std::make_tuple(std::forward(args)...)}; -} - namespace __private__ { template struct ResourceWrapper { T resource; diff --git a/test/c_src/finest.cpp b/test/c_src/finest.cpp index 14c8b9e..d8ae07b 100644 --- a/test/c_src/finest.cpp +++ b/test/c_src/finest.cpp @@ -144,32 +144,32 @@ FINE_NIF(codec_tuple_int64_and_string, 0); Result codec_result_string_int64_ok(ErlNifEnv *, std::string term) { - return fine::ok(term); + return fine::Ok(term); } FINE_NIF(codec_result_string_int64_ok, 0); Result codec_result_string_int64_error(ErlNifEnv *, int64_t term) { - return fine::error(term); + return fine::Error(term); } FINE_NIF(codec_result_string_int64_error, 0); Result codec_result_string_int64_ok_conversion(ErlNifEnv *) { - return fine::ok("fine"); + return fine::Ok("fine"); } FINE_NIF(codec_result_string_int64_ok_conversion, 0); Result codec_result_string_int64_error_conversion(ErlNifEnv *) { uint16_t result = 42; - return fine::error(result); + return fine::Error(result); } FINE_NIF(codec_result_string_int64_error_conversion, 0); std::variant, fine::Error<>> codec_result_int64_string_void_ok_conversion(ErlNifEnv *) { - return fine::ok(static_cast(201702), "c++17"); + return fine::Ok(static_cast(201702), "c++17"); } FINE_NIF(codec_result_int64_string_void_ok_conversion, 0); @@ -197,19 +197,19 @@ FINE_NIF(codec_struct, 0); ExError codec_struct_exception(ErlNifEnv *, ExError term) { return term; } FINE_NIF(codec_struct_exception, 0); -fine::Ok<> codec_ok_empty(ErlNifEnv *) { return fine::ok(); } +fine::Ok<> codec_ok_empty(ErlNifEnv *) { return fine::Ok(); } FINE_NIF(codec_ok_empty, 0); fine::Ok codec_ok_int64(ErlNifEnv *, int64_t term) { - return fine::ok(term); + return fine::Ok(term); } FINE_NIF(codec_ok_int64, 0); -fine::Error<> codec_error_empty(ErlNifEnv *) { return fine::error(); } +fine::Error<> codec_error_empty(ErlNifEnv *) { return fine::Error(); } FINE_NIF(codec_error_empty, 0); fine::Error codec_error_string(ErlNifEnv *, std::string term) { - return fine::error(term); + return fine::Error(term); } FINE_NIF(codec_error_string, 0); From ebdadfae390a8922a72a9cb250208674403911d3 Mon Sep 17 00:00:00 2001 From: Alexis Brodeur Date: Thu, 14 Aug 2025 12:28:02 -0400 Subject: [PATCH 3/4] Remove unnecessary constructor and ensure Items is used --- c_include/fine.hpp | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/c_include/fine.hpp b/c_include/fine.hpp index cf3ef3e..7dc3f5f 100644 --- a/c_include/fine.hpp +++ b/c_include/fine.hpp @@ -147,10 +147,7 @@ template class Ok { public: using Items = std::tuple; - template >> - Ok() : m_items() {} - - explicit Ok(Args... items) : m_items{std::move(items)...} {} + Ok(Args... items) : m_items{std::move(items)...} {} template Ok(const Ok &other) : m_items(other.items()) {} @@ -165,7 +162,7 @@ template class Ok { Items &&items() && noexcept { return std::move(m_items); } private: - std::tuple m_items; + Items m_items; }; // Represents a `:error` tagged tuple, useful as a NIF result. @@ -173,10 +170,7 @@ template class Error { public: using Items = std::tuple; - template >> - Error() : m_items() {} - - explicit Error(Args... items) : m_items{std::move(items)...} {} + Error(Args... items) : m_items{std::move(items)...} {} template Error(const Error &other) : m_items(other.items()) {} @@ -191,7 +185,7 @@ template class Error { Items &&items() && noexcept { return std::move(m_items); } private: - std::tuple m_items; + Items m_items; }; namespace __private__ { From 77a9b843dab1a8ce07f0af3381110005dfe9c640 Mon Sep 17 00:00:00 2001 From: Alexis Brodeur Date: Thu, 14 Aug 2025 12:37:23 -0400 Subject: [PATCH 4/4] Constructors can be explicit --- c_include/fine.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/c_include/fine.hpp b/c_include/fine.hpp index 7dc3f5f..05f0267 100644 --- a/c_include/fine.hpp +++ b/c_include/fine.hpp @@ -147,7 +147,7 @@ template class Ok { public: using Items = std::tuple; - Ok(Args... items) : m_items{std::move(items)...} {} + explicit Ok(Args... items) : m_items{std::move(items)...} {} template Ok(const Ok &other) : m_items(other.items()) {} @@ -170,7 +170,7 @@ template class Error { public: using Items = std::tuple; - Error(Args... items) : m_items{std::move(items)...} {} + explicit Error(Args... items) : m_items{std::move(items)...} {} template Error(const Error &other) : m_items(other.items()) {}