diff --git a/README.md b/README.md index 6574678..9e0fdde 100644 --- a/README.md +++ b/README.md @@ -473,16 +473,24 @@ to usual Elixir functions. Fine provides `Ok` and `Error` types for this purpose. ```c++ -fine::Ok<>() +fine::Ok<> example() { + return fine::Ok(); +} // :ok -fine::Ok(1) +fine::Ok example() { + return fine::Ok(1); +} // {: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"); +} // {:error, "something went wrong"} ``` @@ -490,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 15d0384..05f0267 100644 --- a/c_include/fine.hpp +++ b/c_include/fine.hpp @@ -145,23 +145,47 @@ class Term { // 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>; + explicit Ok(Args... items) : m_items{std::move(items)...} {} + + template + Ok(const Ok &other) : m_items(other.items()) {} + + template + Ok(Ok &&other) : m_items(std::move(other).items()) {} + + const Items &items() const & noexcept { return m_items; } - std::tuple items; + Items &items() & noexcept { return m_items; } + + Items &&items() && noexcept { return std::move(m_items); } + +private: + Items 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>; + explicit Error(Args... items) : m_items{std::move(items)...} {} + + template + Error(const Error &other) : m_items(other.items()) {} + + template + Error(Error &&other) : m_items(std::move(other).items()) {} + + const Items &items() const & noexcept { return m_items; } - std::tuple items; + Items &items() & noexcept { return m_items; } + + Items &&items() && noexcept { return std::move(m_items); } + +private: + Items m_items; }; namespace __private__ { @@ -895,7 +919,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 +931,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..d8ae07b 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; 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