Skip to main content

otter/codec/
tuple.rs

1//! `Encoder`/`Decoder` for Rust tuples <-> Erlang tuples.
2//!
3//! Fixed-arity and element-wise, implemented for arities 1 through 12 (each
4//! element type carries its own `Encoder`/`Decoder`). Encoding builds an N-tuple
5//! from the encoded elements; decoding requires an Erlang tuple of exactly N
6//! elements ([`CodecError::WrongArity`] otherwise) and decodes each in turn.
7
8use std::ffi::c_uint;
9
10use crate::codec::{CodecError, Decoder, Encoder};
11use crate::types::{AnyTerm, Env, Term, Tuple};
12
13macro_rules! tuple_codec {
14    ($($T:ident),+) => {
15        /// Builds an Erlang N-tuple from the encoded elements
16        /// (`enif_make_tuple_from_array`, no heap allocation). Fails if any
17        /// element fails to encode.
18        impl<'id, $($T: Encoder<'id>),+> Encoder<'id> for ($($T,)+) {
19            fn encode(&self, env: impl Env<'id>) -> Result<AnyTerm<'id>, CodecError> {
20                #[allow(non_snake_case)]
21                let ($($T,)+) = self;
22                // Build the tuple from a fixed-size stack array of element words
23                // and one `enif_make_tuple_from_array` — no heap allocation
24                // (`Tuple::from_terms` would `collect` a `Vec`). The array
25                // literal infers `[RawTerm; N]` from `raw_term()`.
26                let raw = [$( $T.encode(env)?.raw_term() ),+];
27                // SAFETY: `env` is live and `raw` holds `raw.len()` contiguous
28                // terms of this brand.
29                let term = unsafe {
30                    enif_ffi::make_tuple_from_array(env.raw_env(), raw.as_ptr(), raw.len() as c_uint)
31                };
32                Ok(AnyTerm::wrap(term, env))
33            }
34        }
35
36        /// Requires an Erlang tuple of exactly N elements
37        /// ([`WrongArity`](CodecError::WrongArity) otherwise), then decodes each
38        /// element in turn; an element's own error propagates.
39        impl<'id, $($T: Decoder<'id>),+> Decoder<'id> for ($($T,)+) {
40            fn decode(term: AnyTerm<'id>, env: impl Env<'id>) -> Result<Self, CodecError> {
41                const ARITY: usize = [$(stringify!($T)),+].len();
42                let view = Tuple::decode(term, env)?.with_elements(env);
43                if view.len() != ARITY {
44                    return Err(CodecError::WrongArity);
45                }
46                let mut index = 0;
47                #[allow(unused_assignments)]
48                let result = ($(
49                    {
50                        let element = $T::decode(view[index], env)?;
51                        index += 1;
52                        element
53                    },
54                )+);
55                Ok(result)
56            }
57        }
58    };
59}
60
61tuple_codec!(A);
62tuple_codec!(A, B);
63tuple_codec!(A, B, C);
64tuple_codec!(A, B, C, D);
65tuple_codec!(A, B, C, D, E);
66tuple_codec!(A, B, C, D, E, F);
67tuple_codec!(A, B, C, D, E, F, G);
68tuple_codec!(A, B, C, D, E, F, G, H);
69tuple_codec!(A, B, C, D, E, F, G, H, I);
70tuple_codec!(A, B, C, D, E, F, G, H, I, J);
71tuple_codec!(A, B, C, D, E, F, G, H, I, J, K);
72tuple_codec!(A, B, C, D, E, F, G, H, I, J, K, L);