diff --git a/c_include/fine.hpp b/c_include/fine.hpp index cb483eb..7140343 100644 --- a/c_include/fine.hpp +++ b/c_include/fine.hpp @@ -65,13 +65,16 @@ inline ERL_NIF_TERM make_atom(ErlNifEnv *env, const char *msg) { // A representation of an atom term. class Atom { public: - Atom(std::string name) : name(name), term(std::nullopt) { + Atom(std::string name) : name(std::move(name)), term(std::nullopt) { if (!Atom::initialized) { Atom::atoms.push_back(this); } } - std::string to_string() const { return this->name; } +public: + const std::string &to_string() const & noexcept { return this->name; } + + std::string to_string() && noexcept { return this->name; } bool operator==(const Atom &other) const { return this->name == other.name; } @@ -90,6 +93,8 @@ class Atom { } friend struct Encoder; + friend struct Decoder; + friend struct ::std::hash; friend int __private__::load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info); @@ -1158,4 +1163,18 @@ inline int load(ErlNifEnv *env, void **, ERL_NIF_TERM) { } // namespace fine +namespace std { +template <> struct hash<::fine::Term> { + size_t operator()(const ::fine::Term &term) noexcept { + return enif_hash(ERL_NIF_INTERNAL_HASH, term, 0); + } +}; + +template <> struct hash<::fine::Atom> { + size_t operator()(const ::fine::Atom &atom) noexcept { + return std::hash{}(atom.to_string()); + } +}; +} // namespace std + #endif diff --git a/test/c_src/finest.cpp b/test/c_src/finest.cpp index ba415bd..bdfed25 100644 --- a/test/c_src/finest.cpp +++ b/test/c_src/finest.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -354,6 +355,17 @@ bool compare_ge(ErlNifEnv *, fine::Term lhs, fine::Term rhs) noexcept { return lhs >= rhs; } FINE_NIF(compare_ge, 0); + +std::uint64_t hash_term(ErlNifEnv *, fine::Term term) noexcept { + return std::invoke(std::hash{}, term); +} +FINE_NIF(hash_term, 0); + +std::uint64_t hash_atom(ErlNifEnv *, fine::Atom atom) noexcept { + return std::invoke(std::hash{}, atom); +} +FINE_NIF(hash_atom, 0); + } // namespace finest FINE_INIT("Elixir.Finest.NIF"); diff --git a/test/lib/finest/nif.ex b/test/lib/finest/nif.ex index c1bbb58..e36f47c 100644 --- a/test/lib/finest/nif.ex +++ b/test/lib/finest/nif.ex @@ -66,5 +66,8 @@ defmodule Finest.NIF do def compare_gt(_lhs, _rhs), do: err!() def compare_ge(_lhs, _rhs), do: err!() + def hash_term(_term), do: err!() + def hash_atom(_term), do: err!() + defp err!(), do: :erlang.nif_error(:not_loaded) end diff --git a/test/test/finest_test.exs b/test/test/finest_test.exs index 347b1c8..be37fb9 100644 --- a/test/test/finest_test.exs +++ b/test/test/finest_test.exs @@ -351,4 +351,18 @@ defmodule FinestTest do assert NIF.compare_ge("fine", "fine") end end + + describe "hash" do + test "term" do + for value <- [42, "fine", ["it", %{"should" => {"just", "work"}}], :atom] do + assert NIF.hash_term(value) == NIF.hash_term(value) + end + end + + test "atom" do + for value <- [:ok, :error, :"with spaces", Enum, nil, true, false] do + assert NIF.hash_atom(value) == NIF.hash_atom(value) + end + end + end end