Skip to main content

libperl_rs/
av.rs

1//! `Av` newtype — `NonNull<AV>` wrapper. Same shape as [`Sv`](crate::Sv)
2//! but for Perl arrays. Mortal-forced construction (see §3.10c in
3//! `docs/plan/README.md`) means the AV is automatically freed at end
4//! of expression unless something else (typically a wrapping `Rv<Av>`)
5//! takes a refcount.
6
7use std::ptr::NonNull;
8
9use libperl_sys::{AV, SV};
10
11use crate::{Perl, Rv, Sv, sv_refcnt_inc};
12
13#[derive(Clone, Copy)]
14#[repr(transparent)]
15pub struct Av(NonNull<AV>);
16
17impl Av {
18    /// Allocate a fresh, empty mortal `AV`.
19    #[inline]
20    pub fn new(perl: &Perl) -> Av {
21        unsafe {
22            // `sys::thx` normalizes the calling convention per function:
23            // on perl < 5.26 no `Perl_newAV` extern exists and the thx
24            // module aliases the macro-generated `newAV` shim instead.
25            let av = libperl_sys::thx::Perl_newAV(perl.as_ptr());
26            // `AV` is layout-compatible with `SV` (it starts with the
27            // SV header) — `sv_2mortal` accepts a `*mut SV` of the AV.
28            crate::thx_call!(perl, Perl_sv_2mortal, av as *mut SV);
29            Av(NonNull::new(av).expect("Perl_newAV returned null"))
30        }
31    }
32
33    /// Append `sv` to the end of the array. The `Sv` is refcount-inc'd
34    /// before being handed to `av_push` because `av_push` takes
35    /// ownership of one ref — and the caller's mortal `Sv` would
36    /// otherwise be freed at scope exit, leaving a dangling slot.
37    #[inline]
38    pub fn push(&self, perl: &Perl, sv: Sv) {
39        unsafe {
40            let inc = sv_refcnt_inc(sv.as_ptr());
41            crate::thx_call!(perl, Perl_av_push, self.0.as_ptr(), inc);
42        }
43    }
44
45    /// Wrap this AV in a fresh mortal `RV` (`\@array` in Perl). The
46    /// returned `Rv<Av>` is the value you typically push to the Perl
47    /// stack as the XS sub's return.
48    #[inline]
49    pub fn into_rv(self, perl: &Perl) -> Rv<Av> {
50        unsafe {
51            // `Perl_newRV` is the refcount-incrementing flavor of the
52            // C `newRV` macro: it bumps the AV's refcount and yields a
53            // fresh RV with refcount 1. Mortalize so it's freed at
54            // scope exit too.
55            let rv = crate::thx_call!(perl, Perl_newRV, self.0.as_ptr() as *mut SV);
56            crate::thx_call!(perl, Perl_sv_2mortal, rv);
57            Rv::from_raw_sv(rv)
58        }
59    }
60
61    /// Wrap a raw `*mut AV` without checking for null. Used by the
62    /// `#[xs_sub]` proc-macro after it has dereferenced an `&Av` arg
63    /// (caller passed `\@arr` and we've already SvROK / SvTYPE-checked
64    /// the SV).
65    ///
66    /// # Safety
67    /// `p` must be non-null and point to a valid AV that outlives the
68    /// returned `Av`.
69    #[inline]
70    pub unsafe fn from_raw_unchecked(p: *mut AV) -> Av {
71        debug_assert!(!p.is_null(), "Av::from_raw_unchecked received a null pointer");
72        Av(unsafe { NonNull::new_unchecked(p) })
73    }
74
75    /// Number of elements (`scalar @array`).
76    #[inline]
77    pub fn len(&self, perl: &Perl) -> usize {
78        // `av_len` returns the highest index, or -1 for empty.
79        let n = unsafe { crate::thx_call!(perl, Perl_av_len, self.0.as_ptr()) };
80        if n < 0 { 0 } else { (n + 1) as usize }
81    }
82
83    /// `$arr[$idx]`, or `None` if the slot is empty / out of bounds.
84    /// The returned `Sv` borrows from this AV — don't keep it past
85    /// any mutation of the AV.
86    #[inline]
87    pub fn get(&self, perl: &Perl, idx: usize) -> Option<Sv> {
88        let svp = unsafe {
89            crate::thx_call!(perl, Perl_av_fetch, self.0.as_ptr(), idx as isize, 0)
90        };
91        if svp.is_null() {
92            return None;
93        }
94        // av_fetch yields `**SV`; deref to get the slot's `*mut SV`.
95        // The slot may itself be null for sparse arrays.
96        Sv::from_raw(unsafe { *svp })
97    }
98
99    /// Iterate over `(0..len)` yielding each slot as `Option<Sv>`.
100    /// `None` for sparse / unallocated slots.
101    #[inline]
102    pub fn iter<'a>(&'a self, perl: &'a Perl) -> AvIter<'a> {
103        let len = self.len(perl);
104        AvIter { perl, av: self.0, idx: 0, len }
105    }
106
107    /// Raw pointer for FFI.
108    #[inline]
109    pub fn as_ptr(&self) -> *mut AV {
110        self.0.as_ptr()
111    }
112}
113
114/// Iterator yielded by [`Av::iter`].
115pub struct AvIter<'a> {
116    perl: &'a Perl,
117    av: NonNull<AV>,
118    idx: usize,
119    len: usize,
120}
121
122impl<'a> Iterator for AvIter<'a> {
123    type Item = Option<Sv>;
124
125    fn next(&mut self) -> Option<Self::Item> {
126        if self.idx >= self.len {
127            return None;
128        }
129        let i = self.idx;
130        self.idx += 1;
131        let svp = unsafe {
132            crate::thx_call!(self.perl, Perl_av_fetch, self.av.as_ptr(), i as isize, 0)
133        };
134        Some(if svp.is_null() {
135            None
136        } else {
137            Sv::from_raw(unsafe { *svp })
138        })
139    }
140
141    fn size_hint(&self) -> (usize, Option<usize>) {
142        let r = self.len - self.idx;
143        (r, Some(r))
144    }
145}