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);