Skip to content

Implicit Conversion for fine::Ok and fine::Error #13

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -473,29 +473,37 @@ to usual Elixir functions. Fine provides `Ok<Args...>` and `Error<Args...>`
types for this purpose.

```c++
fine::Ok<>()
fine::Ok<> example() {
return fine::Ok();
}
// :ok

fine::Ok<int64_t>(1)
fine::Ok<int64_t> example() {
return fine::Ok(1);
}
// {:ok, 1}

fine::Error<>()
fine::Error<> example() {
return fine::Error();
}
// :error

fine::Error<std::string>("something went wrong")
fine::Error<std::string> example() {
return fine::Error("something went wrong");
}
// {:error, "something went wrong"}
```

You can use `std::variant` to express a union of possible result types
a NIF may return:

```c++
std::variant<fine::Ok<int64_t>, fine::Error<std::string>> find_meaning(ErlNifEnv *env) {
if (...) {
return fine::Error<std::string>("something went wrong");
std::variant<fine::Ok<int64_t, int64_t>, fine::Error<std::string>> divmod(ErlNifEnv *env, int64_t a, int64_t b) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

if (b == 0) {
return fine::Error("division by zero");
}

return fine::Ok<int64_t>(42);
return fine::Ok(a / b, a % b);
}
```

Expand Down
44 changes: 34 additions & 10 deletions c_include/fine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,23 +145,47 @@ class Term {
// Represents a `:ok` tagged tuple, useful as a NIF result.
template <typename... Args> class Ok {
public:
Ok(const Args &...items) : items(items...) {}
using Items = std::tuple<Args...>;

private:
friend struct Encoder<Ok<Args...>>;
explicit Ok(Args... items) : m_items{std::move(items)...} {}

template <typename... UArgs>
Ok(const Ok<UArgs...> &other) : m_items(other.items()) {}

template <typename... UArgs>
Ok(Ok<UArgs...> &&other) : m_items(std::move(other).items()) {}

const Items &items() const & noexcept { return m_items; }

std::tuple<Args...> 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 <typename... Args> class Error {
public:
Error(const Args &...items) : items(items...) {}
using Items = std::tuple<Args...>;

private:
friend struct Encoder<Error<Args...>>;
explicit Error(Args... items) : m_items{std::move(items)...} {}

template <typename... UArgs>
Error(const Error<UArgs...> &other) : m_items(other.items()) {}

template <typename... UArgs>
Error(Error<UArgs...> &&other) : m_items(std::move(other).items()) {}

const Items &items() const & noexcept { return m_items; }

std::tuple<Args...> items;
Items &items() & noexcept { return m_items; }

Items &&items() && noexcept { return std::move(m_items); }

private:
Items m_items;
};

namespace __private__ {
Expand Down Expand Up @@ -895,7 +919,7 @@ template <typename... Args> struct Encoder<Ok<Args...>> {
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);
}
Expand All @@ -907,7 +931,7 @@ template <typename... Args> struct Encoder<Error<Args...>> {
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);
}
Expand Down
34 changes: 34 additions & 0 deletions test/c_src/finest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ struct ExError {
static constexpr auto is_exception = true;
};

template <typename T, typename E>
using Result = std::variant<fine::Ok<T>, fine::Error<E>>;

int64_t add(ErlNifEnv *, int64_t x, int64_t y) { return x + y; }
FINE_NIF(add, 0);

Expand Down Expand Up @@ -139,6 +142,37 @@ codec_tuple_int64_and_string(ErlNifEnv *,
}
FINE_NIF(codec_tuple_int64_and_string, 0);

Result<std::string, int64_t> codec_result_string_int64_ok(ErlNifEnv *,
std::string term) {
return fine::Ok(term);
}
FINE_NIF(codec_result_string_int64_ok, 0);

Result<std::string, int64_t> codec_result_string_int64_error(ErlNifEnv *,
int64_t term) {
return fine::Error(term);
}
FINE_NIF(codec_result_string_int64_error, 0);

Result<std::string, int64_t>
codec_result_string_int64_ok_conversion(ErlNifEnv *) {
return fine::Ok("fine");
}
FINE_NIF(codec_result_string_int64_ok_conversion, 0);

Result<std::string, int64_t>
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::Ok<int64_t, std::string>, fine::Error<>>
codec_result_int64_string_void_ok_conversion(ErlNifEnv *) {
return fine::Ok(static_cast<int32_t>(201702), "c++17");
}
FINE_NIF(codec_result_int64_string_void_ok_conversion, 0);

std::vector<int64_t> codec_vector_int64(ErlNifEnv *,
std::vector<int64_t> term) {
return term;
Expand Down
5 changes: 5 additions & 0 deletions test/lib/finest/nif.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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!()
Expand Down
8 changes: 8 additions & 0 deletions test/test/finest_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down