Skip to main content

otter/codec/
integer.rs

1//! `Encoder`/`Decoder` for Rust's fixed-width and pointer-width integers.
2//!
3//! Encoding is infallible — every value fits `enif_make_int64` (signed) or
4//! `enif_make_uint64` (unsigned). Decoding range-checks the Erlang integer into
5//! the target Rust type and reports [`CodecError::IntegerOverflow`] when it does
6//! not fit: a value outside the type's range, a negative decoded into an
7//! unsigned type, or a bignum too wide for the 64-bit read.
8//!
9//! `i128`/`u128` are intentionally absent — the NIF API has no integer primitive
10//! wider than 64 bits. Reading larger bignums (via ETF) is tracked separately
11//! (enhance-14).
12
13use crate::codec::{CodecError, Decoder, Encoder};
14use crate::types::{AnyTerm, Env, Integer};
15
16// Types narrower than 64 bits widen losslessly into the i64/u64 the NIF API
17// takes, and narrow back through `try_from` with a range check.
18macro_rules! narrow_signed {
19    ($($t:ty),+ $(,)?) => { $(
20        /// Widens losslessly into the `i64` of `enif_make_int64`. Infallible.
21        impl<'id> Encoder<'id> for $t {
22            fn encode(&self, env: impl Env<'id>) -> Result<AnyTerm<'id>, CodecError> {
23                Integer::from_i64(env, i64::from(*self)).encode(env)
24            }
25        }
26
27        /// Reads the integer as `i64`, then narrows with a range check —
28        /// [`IntegerOverflow`](CodecError::IntegerOverflow) if it does not fit.
29        impl<'id> Decoder<'id> for $t {
30            fn decode(term: AnyTerm<'id>, env: impl Env<'id>) -> Result<Self, CodecError> {
31                let val = Integer::decode(term, env)?.to_i64(env).ok_or(CodecError::IntegerOverflow)?;
32                Self::try_from(val).map_err(|_| CodecError::IntegerOverflow)
33            }
34        }
35    )+ };
36}
37
38macro_rules! narrow_unsigned {
39    ($($t:ty),+ $(,)?) => { $(
40        /// Widens losslessly into the `u64` of `enif_make_uint64`. Infallible.
41        impl<'id> Encoder<'id> for $t {
42            fn encode(&self, env: impl Env<'id>) -> Result<AnyTerm<'id>, CodecError> {
43                Integer::from_u64(env, u64::from(*self)).encode(env)
44            }
45        }
46
47        /// Reads the integer as `u64`, then narrows with a range check —
48        /// [`IntegerOverflow`](CodecError::IntegerOverflow) on overflow or a
49        /// negative value.
50        impl<'id> Decoder<'id> for $t {
51            fn decode(term: AnyTerm<'id>, env: impl Env<'id>) -> Result<Self, CodecError> {
52                let val = Integer::decode(term, env)?.to_u64(env).ok_or(CodecError::IntegerOverflow)?;
53                Self::try_from(val).map_err(|_| CodecError::IntegerOverflow)
54            }
55        }
56    )+ };
57}
58
59narrow_signed!(i8, i16, i32);
60narrow_unsigned!(u8, u16, u32);
61
62// The exact 64-bit types map straight onto the NIF primitives — no cast, and the
63// only decode failure is a bignum that does not fit the 64-bit read.
64/// Maps straight onto `enif_make_int64`. Infallible.
65impl<'id> Encoder<'id> for i64 {
66    fn encode(&self, env: impl Env<'id>) -> Result<AnyTerm<'id>, CodecError> {
67        Integer::from_i64(env, *self).encode(env)
68    }
69}
70
71/// Reads via `enif_get_int64`; the only failure is a bignum too wide for the
72/// 64-bit read ([`IntegerOverflow`](CodecError::IntegerOverflow)).
73impl<'id> Decoder<'id> for i64 {
74    fn decode(term: AnyTerm<'id>, env: impl Env<'id>) -> Result<Self, CodecError> {
75        Integer::decode(term, env)?.to_i64(env).ok_or(CodecError::IntegerOverflow)
76    }
77}
78
79/// Maps straight onto `enif_make_uint64`. Infallible.
80impl<'id> Encoder<'id> for u64 {
81    fn encode(&self, env: impl Env<'id>) -> Result<AnyTerm<'id>, CodecError> {
82        Integer::from_u64(env, *self).encode(env)
83    }
84}
85
86/// Reads via `enif_get_uint64`; fails with
87/// [`IntegerOverflow`](CodecError::IntegerOverflow) on a negative value or a
88/// bignum too wide for the 64-bit read.
89impl<'id> Decoder<'id> for u64 {
90    fn decode(term: AnyTerm<'id>, env: impl Env<'id>) -> Result<Self, CodecError> {
91        Integer::decode(term, env)?.to_u64(env).ok_or(CodecError::IntegerOverflow)
92    }
93}
94
95// `isize`/`usize` are 64-bit on every otter target, but Rust offers no `From`
96// to/from `i64`/`u64` (the width is platform-dependent), so they cast explicitly
97// and range-check on the way back in.
98/// 64-bit on every otter target; casts to `i64` and encodes. Infallible.
99impl<'id> Encoder<'id> for isize {
100    fn encode(&self, env: impl Env<'id>) -> Result<AnyTerm<'id>, CodecError> {
101        Integer::from_i64(env, *self as i64).encode(env)
102    }
103}
104
105/// Reads as `i64` and range-checks into `isize`
106/// ([`IntegerOverflow`](CodecError::IntegerOverflow) if it does not fit).
107impl<'id> Decoder<'id> for isize {
108    fn decode(term: AnyTerm<'id>, env: impl Env<'id>) -> Result<Self, CodecError> {
109        let val = Integer::decode(term, env)?.to_i64(env).ok_or(CodecError::IntegerOverflow)?;
110        Self::try_from(val).map_err(|_| CodecError::IntegerOverflow)
111    }
112}
113
114/// 64-bit on every otter target; casts to `u64` and encodes. Infallible.
115impl<'id> Encoder<'id> for usize {
116    fn encode(&self, env: impl Env<'id>) -> Result<AnyTerm<'id>, CodecError> {
117        Integer::from_u64(env, *self as u64).encode(env)
118    }
119}
120
121/// Reads as `u64` and range-checks into `usize`
122/// ([`IntegerOverflow`](CodecError::IntegerOverflow) on a negative value or
123/// overflow).
124impl<'id> Decoder<'id> for usize {
125    fn decode(term: AnyTerm<'id>, env: impl Env<'id>) -> Result<Self, CodecError> {
126        let val = Integer::decode(term, env)?.to_u64(env).ok_or(CodecError::IntegerOverflow)?;
127        Self::try_from(val).map_err(|_| CodecError::IntegerOverflow)
128    }
129}