Skip to content

Commit 662c321

Browse files
committed
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.
1 parent b3dee74 commit 662c321

File tree

5 files changed

+128
-21
lines changed

5 files changed

+128
-21
lines changed

README.md

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -470,19 +470,28 @@ When it comes to NIFs, errors often indicate unexpected failures and
470470
raising an exception makes sense, however you may also want to handle
471471
certain errors gracefully by returning `:ok`/`:error` tuples, similarly
472472
to usual Elixir functions. Fine provides `Ok<Args...>` and `Error<Args...>`
473-
types for this purpose.
473+
types for this purpose, along with their respective factories `fine::ok` and
474+
`fine::error`:
474475
475476
```c++
476-
fine::Ok<>()
477+
fine::Ok<> example() {
478+
return fine::ok();
479+
}
477480
// :ok
478481
479-
fine::Ok<int64_t>(1)
482+
fine::Ok<int64_t> example() {
483+
return fine::ok(1); // implicit conversion
484+
}
480485
// {:ok, 1}
481486
482-
fine::Error<>()
487+
fine::Error<> example() {
488+
return fine::error();
489+
}
483490
// :error
484491
485-
fine::Error<std::string>("something went wrong")
492+
fine::Error<std::string> example() {
493+
return fine::error("something went wrong"); // implicit conversion
494+
}
486495
// {:error, "something went wrong"}
487496
```
488497
@@ -492,10 +501,10 @@ a NIF may return:
492501
```c++
493502
std::variant<fine::Ok<int64_t>, fine::Error<std::string>> find_meaning(ErlNifEnv *env) {
494503
if (...) {
495-
return fine::Error<std::string>("something went wrong");
504+
return fine::error("something went wrong");
496505
}
497506
498-
return fine::Ok<int64_t>(42);
507+
return fine::ok(42);
499508
}
500509
```
501510

c_include/fine.hpp

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -142,28 +142,79 @@ class Term {
142142
ERL_NIF_TERM term;
143143
};
144144

145+
namespace __private__ {
146+
template <class T> struct UnwrapRefwrapper {
147+
using type = T;
148+
};
149+
150+
template <class T> struct UnwrapRefwrapper<std::reference_wrapper<T>> {
151+
using type = T &;
152+
};
153+
154+
template <class T>
155+
using UnwrapDecayType =
156+
typename UnwrapRefwrapper<typename std::decay<T>::type>::type;
157+
} // namespace __private__
158+
145159
// Represents a `:ok` tagged tuple, useful as a NIF result.
146160
template <typename... Args> class Ok {
147161
public:
148-
Ok(const Args &...items) : items(items...) {}
162+
using Items = std::tuple<Args...>;
149163

150-
private:
151-
friend struct Encoder<Ok<Args...>>;
164+
Ok(Items &&items) : m_items(std::forward<Items>(items)) {}
165+
166+
template <typename... UArgs>
167+
Ok(const Ok<UArgs...> &other) : m_items{other.items()} {}
168+
169+
template <typename... UArgs>
170+
Ok(Ok<UArgs...> &&other) : m_items{other.items()} {}
171+
172+
const Items &items() const & noexcept { return m_items; }
173+
174+
Items &items() & noexcept { return m_items; }
175+
176+
Items &&items() && noexcept { return m_items; }
152177

153-
std::tuple<Args...> items;
178+
private:
179+
std::tuple<Args...> m_items;
154180
};
155181

156182
// Represents a `:error` tagged tuple, useful as a NIF result.
157183
template <typename... Args> class Error {
158184
public:
159-
Error(const Args &...items) : items(items...) {}
185+
using Items = std::tuple<Args...>;
160186

161-
private:
162-
friend struct Encoder<Error<Args...>>;
187+
Error(Items &&items) : m_items(std::forward<Items>(items)) {}
188+
189+
template <typename... UArgs>
190+
Error(const Error<UArgs...> &other) : m_items{other.items()} {}
191+
192+
template <typename... UArgs>
193+
Error(Error<UArgs...> &&other) : m_items{other.items()} {}
194+
195+
const Items &items() const & noexcept { return m_items; }
196+
197+
Items &items() & noexcept { return m_items; }
163198

164-
std::tuple<Args...> items;
199+
Items &&items() && noexcept { return m_items; }
200+
201+
private:
202+
std::tuple<Args...> m_items;
165203
};
166204

205+
template <typename... Args>
206+
inline static Ok<__private__::UnwrapDecayType<Args>...> ok(Args &&...args) {
207+
return Ok<__private__::UnwrapDecayType<Args>...>{
208+
std::make_tuple(std::forward<Args>(args)...)};
209+
}
210+
211+
template <typename... Args>
212+
inline static Error<__private__::UnwrapDecayType<Args>...>
213+
error(Args &&...args) {
214+
return Error<__private__::UnwrapDecayType<Args>...>{
215+
std::make_tuple(std::forward<Args>(args)...)};
216+
}
217+
167218
namespace __private__ {
168219
template <typename T> struct ResourceWrapper {
169220
T resource;
@@ -895,7 +946,7 @@ template <typename... Args> struct Encoder<Ok<Args...>> {
895946
auto tag = __private__::atoms::ok;
896947

897948
if constexpr (sizeof...(Args) > 0) {
898-
return fine::encode(env, std::tuple_cat(std::tuple(tag), ok.items));
949+
return fine::encode(env, std::tuple_cat(std::tuple(tag), ok.items()));
899950
} else {
900951
return fine::encode(env, tag);
901952
}
@@ -907,7 +958,7 @@ template <typename... Args> struct Encoder<Error<Args...>> {
907958
auto tag = __private__::atoms::error;
908959

909960
if constexpr (sizeof...(Args) > 0) {
910-
return fine::encode(env, std::tuple_cat(std::tuple(tag), error.items));
961+
return fine::encode(env, std::tuple_cat(std::tuple(tag), error.items()));
911962
} else {
912963
return fine::encode(env, tag);
913964
}

test/c_src/finest.cpp

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ struct ExError {
7676
static constexpr auto is_exception = true;
7777
};
7878

79+
template <typename T, typename E>
80+
using Result = std::variant<fine::Ok<T>, fine::Error<E>>;
81+
7982
int64_t add(ErlNifEnv *, int64_t x, int64_t y) { return x + y; }
8083
FINE_NIF(add, 0);
8184

@@ -139,6 +142,37 @@ codec_tuple_int64_and_string(ErlNifEnv *,
139142
}
140143
FINE_NIF(codec_tuple_int64_and_string, 0);
141144

145+
Result<std::string, int64_t> codec_result_string_int64_ok(ErlNifEnv *,
146+
std::string term) {
147+
return fine::ok(term);
148+
}
149+
FINE_NIF(codec_result_string_int64_ok, 0);
150+
151+
Result<std::string, int64_t> codec_result_string_int64_error(ErlNifEnv *,
152+
int64_t term) {
153+
return fine::error(term);
154+
}
155+
FINE_NIF(codec_result_string_int64_error, 0);
156+
157+
Result<std::string, int64_t>
158+
codec_result_string_int64_ok_conversion(ErlNifEnv *) {
159+
return fine::ok("fine");
160+
}
161+
FINE_NIF(codec_result_string_int64_ok_conversion, 0);
162+
163+
Result<std::string, int64_t>
164+
codec_result_string_int64_error_conversion(ErlNifEnv *) {
165+
uint16_t result = 42;
166+
return fine::error(result);
167+
}
168+
FINE_NIF(codec_result_string_int64_error_conversion, 0);
169+
170+
std::variant<fine::Ok<int64_t, std::string>, fine::Error<>>
171+
codec_result_int64_string_void_ok_conversion(ErlNifEnv *) {
172+
return fine::ok(static_cast<int32_t>(201702), "c++17");
173+
}
174+
FINE_NIF(codec_result_int64_string_void_ok_conversion, 0);
175+
142176
std::vector<int64_t> codec_vector_int64(ErlNifEnv *,
143177
std::vector<int64_t> term) {
144178
return term;
@@ -163,19 +197,19 @@ FINE_NIF(codec_struct, 0);
163197
ExError codec_struct_exception(ErlNifEnv *, ExError term) { return term; }
164198
FINE_NIF(codec_struct_exception, 0);
165199

166-
fine::Ok<> codec_ok_empty(ErlNifEnv *) { return fine::Ok(); }
200+
fine::Ok<> codec_ok_empty(ErlNifEnv *) { return fine::ok(); }
167201
FINE_NIF(codec_ok_empty, 0);
168202

169203
fine::Ok<int64_t> codec_ok_int64(ErlNifEnv *, int64_t term) {
170-
return fine::Ok(term);
204+
return fine::ok(term);
171205
}
172206
FINE_NIF(codec_ok_int64, 0);
173207

174-
fine::Error<> codec_error_empty(ErlNifEnv *) { return fine::Error(); }
208+
fine::Error<> codec_error_empty(ErlNifEnv *) { return fine::error(); }
175209
FINE_NIF(codec_error_empty, 0);
176210

177211
fine::Error<std::string> codec_error_string(ErlNifEnv *, std::string term) {
178-
return fine::Error(term);
212+
return fine::error(term);
179213
}
180214
FINE_NIF(codec_error_string, 0);
181215

test/lib/finest/nif.ex

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ defmodule Finest.NIF do
2828
def codec_optional_int64(_term), do: err!()
2929
def codec_variant_int64_or_string(_term), do: err!()
3030
def codec_tuple_int64_and_string(_term), do: err!()
31+
def codec_result_string_int64_ok(_term), do: err!()
32+
def codec_result_string_int64_error(_term), do: err!()
33+
def codec_result_string_int64_ok_conversion(), do: err!()
34+
def codec_result_string_int64_error_conversion(), do: err!()
35+
def codec_result_int64_string_void_ok_conversion(), do: err!()
3136
def codec_vector_int64(_term), do: err!()
3237
def codec_map_atom_int64(_term), do: err!()
3338
def codec_resource(_term), do: err!()

test/test/finest_test.exs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,14 @@ defmodule FinestTest do
217217
assert NIF.codec_error_empty() == :error
218218
assert NIF.codec_error_string("this is the reason") == {:error, "this is the reason"}
219219
end
220+
221+
test "result" do
222+
assert NIF.codec_result_string_int64_ok("fine") == {:ok, "fine"}
223+
assert NIF.codec_result_string_int64_error(42) == {:error, 42}
224+
assert NIF.codec_result_string_int64_ok_conversion() == {:ok, "fine"}
225+
assert NIF.codec_result_string_int64_error_conversion() == {:error, 42}
226+
assert NIF.codec_result_int64_string_void_ok_conversion() == {:ok, 201_702, "c++17"}
227+
end
220228
end
221229

222230
describe "resource" do

0 commit comments

Comments
 (0)