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}