Skip to content

Commit 589dd0b

Browse files
committed
Add enif_hash support
This is related to #10, and will allow `fine::Atom`s and `fine::Term`s to be used as `std::map`, and `std::unordered_map` keys.
1 parent 983fadb commit 589dd0b

File tree

4 files changed

+70
-0
lines changed

4 files changed

+70
-0
lines changed

c_include/fine.hpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,15 @@ template <typename T> ERL_NIF_TERM encode(ErlNifEnv *env, const T &value);
4444
template <typename T, typename SFINAE = void> struct Decoder;
4545
template <typename T, typename SFINAE = void> struct Encoder;
4646

47+
enum class HashAlgorithm {
48+
INTERNAL = ERL_NIF_INTERNAL_HASH,
49+
PHASH2 = ERL_NIF_PHASH2,
50+
};
51+
52+
namespace __private__ {
53+
template <typename T> struct Hasher;
54+
}
55+
4756
namespace __private__ {
4857
std::vector<ErlNifFunc> &get_erl_nif_funcs();
4958
int load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info);
@@ -90,6 +99,7 @@ class Atom {
9099
}
91100

92101
friend struct Encoder<Atom>;
102+
friend struct __private__::Hasher<Atom>;
93103

94104
friend int __private__::load(ErlNifEnv *env, void **priv_data,
95105
ERL_NIF_TERM load_info);
@@ -1101,6 +1111,34 @@ inline int load(ErlNifEnv *env, void **, ERL_NIF_TERM) {
11011111
}
11021112
} // namespace __private__
11031113

1114+
// Hash
1115+
namespace __private__ {
1116+
template <> struct Hasher<Atom> {
1117+
static std::uint64_t hash(HashAlgorithm algorithm, const Atom &atom,
1118+
std::uint64_t salt = 0) noexcept {
1119+
return enif_hash(static_cast<ErlNifHash>(algorithm), *atom.term, salt);
1120+
}
1121+
};
1122+
1123+
template <> struct Hasher<Term> {
1124+
static std::uint64_t hash(HashAlgorithm algorithm, const Term &term,
1125+
std::uint64_t salt = 0) noexcept {
1126+
return enif_hash(static_cast<ErlNifHash>(algorithm), term, salt);
1127+
}
1128+
};
1129+
} // namespace __private__
1130+
1131+
template <HashAlgorithm A = HashAlgorithm::INTERNAL, typename T>
1132+
inline static std::uint64_t hash(const T &value, std::uint64_t salt = 0) {
1133+
return __private__::Hasher<T>::hash(A, value, salt);
1134+
}
1135+
1136+
template <typename T>
1137+
inline static std::uint64_t hash(HashAlgorithm algorithm, const T &value,
1138+
std::uint64_t salt = 0) {
1139+
return __private__::Hasher<T>::hash(algorithm, value, salt);
1140+
}
1141+
11041142
// Macros
11051143

11061144
#define FINE_NIF(name, flags) \
@@ -1158,4 +1196,18 @@ inline int load(ErlNifEnv *env, void **, ERL_NIF_TERM) {
11581196

11591197
} // namespace fine
11601198

1199+
namespace std {
1200+
template <> struct hash<::fine::Term> {
1201+
size_t operator()(const ::fine::Term &term) noexcept {
1202+
return ::fine::hash(term);
1203+
}
1204+
};
1205+
1206+
template <> struct hash<::fine::Atom> {
1207+
size_t operator()(const ::fine::Term &term) noexcept {
1208+
return ::fine::hash(term);
1209+
}
1210+
};
1211+
} // namespace std
1212+
11611213
#endif

test/c_src/finest.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,14 @@ bool compare_ge(ErlNifEnv *, fine::Term lhs, fine::Term rhs) noexcept {
354354
return lhs >= rhs;
355355
}
356356
FINE_NIF(compare_ge, 0);
357+
358+
std::uint64_t hash_test(ErlNifEnv *, fine::Term term) noexcept {
359+
// Ensure the use of PHASH2. INTERNAL is not guaranteed to be stable across
360+
// ERTS instances, even less so ERTS versions.
361+
return fine::hash<fine::HashAlgorithm::PHASH2>(term);
362+
}
363+
FINE_NIF(hash_test, 0);
364+
357365
} // namespace finest
358366

359367
FINE_INIT("Elixir.Finest.NIF");

test/lib/finest/nif.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,5 +66,7 @@ defmodule Finest.NIF do
6666
def compare_gt(_lhs, _rhs), do: err!()
6767
def compare_ge(_lhs, _rhs), do: err!()
6868

69+
def hash_test(_term), do: err!()
70+
6971
defp err!(), do: :erlang.nif_error(:not_loaded)
7072
end

test/test/finest_test.exs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,4 +351,12 @@ defmodule FinestTest do
351351
assert NIF.compare_ge("fine", "fine")
352352
end
353353
end
354+
355+
describe "hash" do
356+
test "phash2" do
357+
for elem <- [42, "fine", ["it", %{"should" => {"just", "work"}}]] do
358+
assert NIF.hash_test(elem) == :erlang.phash2(elem)
359+
end
360+
end
361+
end
354362
end

0 commit comments

Comments
 (0)