Skip to main content

otter/codec/
list.rs

1//! `Encoder`/`Decoder` for Rust `[T]`/`Vec<T>` <-> Erlang lists.
2//!
3//! Encoding a slice or `Vec` builds a proper list from the element-wise
4//! encodings. Decoding requires a *proper* list — an improper tail yields
5//! [`CodecError::WrongType`] — and decodes each element into the `Vec`. An
6//! element that fails to encode/decode propagates its own error.
7
8use std::ffi::c_uint;
9
10use crate::codec::{CodecError, Decoder, Encoder};
11use crate::types::{AnyTerm, Env, List, RawTerm, Term};
12
13/// Builds a proper Erlang list from the element-wise encodings in one
14/// `enif_make_list_from_array`. Fails if any element fails to encode.
15impl<'id, T: Encoder<'id>> Encoder<'id> for [T] {
16    fn encode(&self, env: impl Env<'id>) -> Result<AnyTerm<'id>, CodecError> {
17        // Collect the encoded element words directly and build the list in one
18        // `enif_make_list_from_array`. Going through `List::from_terms` would
19        // re-collect a second `Vec` of the same words, so we inline it here to
20        // keep a single allocation for the whole list.
21        let mut raw: Vec<RawTerm> = Vec::with_capacity(self.len());
22        for item in self {
23            raw.push(item.encode(env)?.raw_term());
24        }
25        // SAFETY: `env` is live for this call, and `raw` holds `raw.len()`
26        // contiguous terms of this brand.
27        let term =
28            unsafe { enif_ffi::make_list_from_array(env.raw_env(), raw.as_ptr(), raw.len() as c_uint) };
29        Ok(AnyTerm::wrap(term, env))
30    }
31}
32
33/// Encodes as a proper list, via the `[T]` impl.
34impl<'id, T: Encoder<'id>> Encoder<'id> for Vec<T> {
35    fn encode(&self, env: impl Env<'id>) -> Result<AnyTerm<'id>, CodecError> {
36        self.as_slice().encode(env)
37    }
38}
39
40/// Requires a *proper* list, decoding each element into the `Vec`. An improper
41/// tail is [`WrongType`](CodecError::WrongType); an element's own error
42/// propagates.
43impl<'id, T: Decoder<'id>> Decoder<'id> for Vec<T> {
44    fn decode(term: AnyTerm<'id>, env: impl Env<'id>) -> Result<Self, CodecError> {
45        let list = List::decode(term, env)?;
46        // Single traversal: walk the cons cells with `enif_get_list_cell`,
47        // decoding each head, then confirm the terminal is `[]` — an improper
48        // list is rejected. (The old path additionally walked the whole list up
49        // front via `enif_get_list_length` just to size the `Vec`.)
50        let mut out = Vec::new();
51        let mut iter = list.iter(env);
52        for head in &mut iter {
53            out.push(T::decode(head, env)?);
54        }
55        let tail = iter.tail().expect("ListIterator yields its terminal once exhausted");
56        // SAFETY: `env` is live and `tail` is a term of this brand.
57        if unsafe { enif_ffi::is_empty_list(env.raw_env(), tail.raw_term()) } != 0 {
58            Ok(out)
59        } else {
60            Err(CodecError::WrongType)
61        }
62    }
63}