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}