Skip to main content

libperl_rs/
hv.rs

1//! `Hv` newtype — `NonNull<HV>` wrapper. Same shape as [`Av`](crate::Av)
2//! but for Perl hashes. Mortal-forced construction.
3
4use std::ptr::NonNull;
5
6use libperl_sys::{HV, SV};
7
8use crate::{Perl, Rv, Sv, sv_refcnt_inc};
9
10#[derive(Clone, Copy)]
11#[repr(transparent)]
12pub struct Hv(NonNull<HV>);
13
14impl Hv {
15    /// Allocate a fresh, empty mortal `HV`.
16    #[inline]
17    pub fn new(perl: &Perl) -> Hv {
18        unsafe {
19            let hv = crate::thx_call!(perl, Perl_newHV,);
20            crate::thx_call!(perl, Perl_sv_2mortal, hv as *mut SV);
21            Hv(NonNull::new(hv).expect("Perl_newHV returned null"))
22        }
23    }
24
25    /// `$hv{$key} = $val` — store `val` under `key`. Like
26    /// [`Av::push`](crate::Av::push), the value is refcount-inc'd
27    /// before being handed off because `hv_store` takes ownership of
28    /// one ref.
29    pub fn store(&self, perl: &Perl, key: &str, val: Sv) {
30        let bytes = key.as_bytes();
31        unsafe {
32            let inc = sv_refcnt_inc(val.as_ptr());
33            // `klen` is `I32` — caller-side cast; len always fits for
34            // realistic hash keys, no overflow check.
35            crate::thx_call!(
36                perl,
37                Perl_hv_store,
38                self.0.as_ptr(),
39                bytes.as_ptr() as *const ::core::ffi::c_char,
40                bytes.len() as _,
41                inc,
42                0, // hash = 0 → let perl compute
43            );
44        }
45    }
46
47    /// Wrap this HV in a fresh mortal `RV` (`\%hash` in Perl).
48    #[inline]
49    pub fn into_rv(self, perl: &Perl) -> Rv<Hv> {
50        unsafe {
51            let rv = crate::thx_call!(perl, Perl_newRV, self.0.as_ptr() as *mut SV);
52            crate::thx_call!(perl, Perl_sv_2mortal, rv);
53            Rv::from_raw_sv(rv)
54        }
55    }
56
57    /// Wrap a raw `*mut HV` without checking for null. Used by the
58    /// `#[xs_sub]` proc-macro after it has dereferenced an `&Hv` arg.
59    ///
60    /// # Safety
61    /// `p` must be non-null and point to a valid HV that outlives the
62    /// returned `Hv`.
63    #[inline]
64    pub unsafe fn from_raw_unchecked(p: *mut HV) -> Hv {
65        debug_assert!(!p.is_null(), "Hv::from_raw_unchecked received a null pointer");
66        Hv(unsafe { NonNull::new_unchecked(p) })
67    }
68
69    /// Iterate over `(key, value)` pairs. Resets the hash's iterator
70    /// state on entry, so a single live `HvIter` is fine; nested or
71    /// concurrent iteration over the same HV will interfere.
72    ///
73    /// Keys come back as `&[u8]` (raw bytes) — Perl hash keys aren't
74    /// required to be UTF-8. Borrows last as long as the iterator;
75    /// don't mutate the HV while iterating.
76    #[inline]
77    pub fn iter<'a>(&'a self, perl: &'a Perl) -> HvIter<'a> {
78        unsafe { crate::thx_call!(perl, Perl_hv_iterinit, self.0.as_ptr()); }
79        HvIter { perl, hv: self.0, _marker: ::core::marker::PhantomData }
80    }
81
82    /// Raw pointer for FFI.
83    #[inline]
84    pub fn as_ptr(&self) -> *mut HV {
85        self.0.as_ptr()
86    }
87}
88
89/// Iterator yielded by [`Hv::iter`].
90pub struct HvIter<'a> {
91    perl: &'a Perl,
92    hv: NonNull<HV>,
93    // Conceptually borrows the HV's bucket storage for the key slice.
94    _marker: ::core::marker::PhantomData<&'a HV>,
95}
96
97impl<'a> Iterator for HvIter<'a> {
98    type Item = (&'a [u8], Sv);
99
100    fn next(&mut self) -> Option<Self::Item> {
101        let mut key_ptr: *mut ::core::ffi::c_char = ::core::ptr::null_mut();
102        let mut keylen: libperl_sys::I32 = 0;
103        let val = unsafe {
104            crate::thx_call!(
105                self.perl,
106                Perl_hv_iternextsv,
107                self.hv.as_ptr(),
108                &mut key_ptr,
109                &mut keylen,
110            )
111        };
112        if val.is_null() {
113            return None;
114        }
115        let key = unsafe {
116            ::core::slice::from_raw_parts(key_ptr as *const u8, keylen as usize)
117        };
118        let sv = unsafe { Sv::from_raw_unchecked(val) };
119        Some((key, sv))
120    }
121}