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