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    /// The hash's name (`HvNAME`) — non-`None` exactly for stashes
83    /// (package symbol tables), whose name is the package name.
84    pub fn name(&self) -> Option<String> {
85        let p = unsafe { libperl_sys::HvNAME(self.0.as_ptr()) };
86        if p.is_null() {
87            None
88        } else {
89            Some(
90                unsafe { std::ffi::CStr::from_ptr(p) }
91                    .to_string_lossy()
92                    .into_owned(),
93            )
94        }
95    }
96
97    /// Raw pointer for FFI.
98    #[inline]
99    pub fn as_ptr(&self) -> *mut HV {
100        self.0.as_ptr()
101    }
102}
103
104/// Iterator yielded by [`Hv::iter`].
105pub struct HvIter<'a> {
106    perl: &'a Perl,
107    hv: NonNull<HV>,
108    // Conceptually borrows the HV's bucket storage for the key slice.
109    _marker: ::core::marker::PhantomData<&'a HV>,
110}
111
112impl<'a> Iterator for HvIter<'a> {
113    type Item = (&'a [u8], Sv);
114
115    fn next(&mut self) -> Option<Self::Item> {
116        let mut key_ptr: *mut ::core::ffi::c_char = ::core::ptr::null_mut();
117        let mut keylen: libperl_sys::I32 = 0;
118        let val = unsafe {
119            crate::thx_call!(
120                self.perl,
121                Perl_hv_iternextsv,
122                self.hv.as_ptr(),
123                &mut key_ptr,
124                &mut keylen,
125            )
126        };
127        if val.is_null() {
128            return None;
129        }
130        let key = unsafe {
131            ::core::slice::from_raw_parts(key_ptr as *const u8, keylen as usize)
132        };
133        let sv = unsafe { Sv::from_raw_unchecked(val) };
134        Some((key, sv))
135    }
136}