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}