Skip to main content

otter/codec/
map.rs

1//! `Encoder`/`Decoder` for `std::collections::HashMap` <-> Erlang maps.
2//!
3//! Encoding builds the map in one `enif_make_map_from_arrays` from the encoded
4//! keys and values (falling back to incremental insertion only if the encoded
5//! keys collide). Decoding iterates the map's pairs and decodes each into the
6//! `HashMap`. Both
7//! sides are generic over the hasher `S`, so a non-default `BuildHasher` works.
8//! Keys and values that fail to encode/decode propagate their own error.
9
10use std::collections::HashMap;
11use std::hash::{BuildHasher, Hash};
12
13use crate::codec::{CodecError, Decoder, Encoder};
14use crate::types::{AnyTerm, Env, Map, RawTerm, Term};
15
16/// Builds an Erlang map from the encoded keys and values in one
17/// `enif_make_map_from_arrays`, falling back to incremental insertion only if
18/// the encoded keys collide (a non-injective key `Encoder`). Fails if any
19/// key/value fails to encode.
20impl<'id, K: Encoder<'id>, V: Encoder<'id>, S> Encoder<'id> for HashMap<K, V, S> {
21    fn encode(&self, env: impl Env<'id>) -> Result<AnyTerm<'id>, CodecError> {
22        // Encode keys and values into parallel arrays and build the map in one
23        // `enif_make_map_from_arrays`, rather than threading N intermediate map
24        // terms through `enif_make_map_put`.
25        let mut keys: Vec<RawTerm> = Vec::with_capacity(self.len());
26        let mut vals: Vec<RawTerm> = Vec::with_capacity(self.len());
27        for (key, value) in self {
28            keys.push(key.encode(env)?.raw_term());
29            vals.push(value.encode(env)?.raw_term());
30        }
31        let mut out: RawTerm = 0;
32        // SAFETY: `env` is live; `keys`/`vals` are equal-length contiguous arrays
33        // of `keys.len()` terms of this brand; `out` receives the map on success.
34        let ok = unsafe {
35            enif_ffi::make_map_from_arrays(env.raw_env(), keys.as_ptr(), vals.as_ptr(), keys.len(), &mut out)
36        };
37        if ok != 0 {
38            return Ok(AnyTerm::wrap(out, env));
39        }
40        // `make_map_from_arrays` returns 0 only on duplicate keys. Distinct Rust
41        // keys can collide as Erlang terms only with a non-injective `Encoder`;
42        // fall back to incremental `put` (last write wins), reusing the terms we
43        // already encoded.
44        let mut map = Map::new(env);
45        for (k, v) in keys.iter().zip(&vals) {
46            map = map.put(env, AnyTerm::wrap(*k, env), AnyTerm::wrap(*v, env));
47        }
48        map.encode(env)
49    }
50}
51
52/// Requires an Erlang map, decoding each pair into the `HashMap`. Generic over
53/// the hasher `S`. A key/value's own error propagates.
54impl<'id, K, V, S> Decoder<'id> for HashMap<K, V, S>
55where
56    K: Decoder<'id> + Eq + Hash,
57    V: Decoder<'id>,
58    S: BuildHasher + Default,
59{
60    fn decode(term: AnyTerm<'id>, env: impl Env<'id>) -> Result<Self, CodecError> {
61        let map = Map::decode(term, env)?;
62        let mut out = HashMap::with_capacity_and_hasher(map.size(env), S::default());
63        for (key, value) in map.iter(env) {
64            out.insert(K::decode(key, env)?, V::decode(value, env)?);
65        }
66        Ok(out)
67    }
68}