sdecay 0.1.0

Bindings for SandiaDecay C++ library, used to compute nuclide mixtures
Documentation
//! Defines Rust wrapper around C++ `std::string`

use core::{
    ffi::CStr,
    fmt::{Debug, Display},
    hash::Hash,
};

use crate::{
    containers, impl_moveable,
    wrapper::{BindgenString, Wrapper},
};

/// Rust representation of C++ `std::string`
///
/// You should not try to construct this type, and always interact with it behind [`Container`](crate::container::Container) implementation
#[repr(C)]
pub struct StdString {
    /// Actual `std::string` as generated by bindgen
    inner: BindgenString,
    /// This struct should never be moved out of pin
    _pin: core::marker::PhantomPinned,
    /// This struct should never be constructed
    _private: core::marker::PhantomData<()>,
}

const _: () = const {
    use core::mem::{align_of, offset_of, size_of};
    assert!(
        offset_of!(StdString, inner) == 0,
        "Offset of inner std::string"
    );
    assert!(
        size_of::<StdString>() == size_of::<BindgenString>(),
        "Size of inner std::string"
    );
    assert!(
        align_of::<StdString>() == align_of::<BindgenString>(),
        "Size of inner std::string"
    );
};

impl Wrapper for StdString {
    type CSide = BindgenString;
}

impl_moveable!(string, StdString);

impl Debug for StdString {
    #[inline]
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        #[cfg(feature = "alloc")]
        {
            Debug::fmt(&self.as_str(), f)
        }
        #[cfg(not(feature = "alloc"))]
        {
            Debug::fmt(&self.as_cstr(), f)
        }
    }
}

impl Drop for StdString {
    fn drop(&mut self) {
        // SAFETY: ffi call forwarded to destructor of `std::string`
        unsafe { sdecay_sys::sdecay::std_string_destruct(self.ptr_mut()) };
    }
}

impl StdString {
    #[inline]
    pub(crate) fn ptr(&self) -> *const BindgenString {
        core::ptr::from_ref(&self.inner)
    }

    #[inline]
    pub(crate) fn ptr_mut(&mut self) -> *mut BindgenString {
        core::ptr::from_mut(&mut self.inner)
    }

    #[expect(missing_docs)]
    #[inline]
    pub fn as_cstr(&self) -> &CStr {
        // SAFETY: ffi call forwarded to <https://cplusplus.com/reference/string/string/c_str/>
        let ptr = unsafe { sdecay_sys::sdecay::std_string_cstr(self.ptr()) };
        // SAFETY: returned ptr cannot be null, and points to valid null-terminated buffer (see doc referenced above)
        unsafe { CStr::from_ptr(ptr) }
    }

    #[expect(missing_docs)]
    #[inline]
    pub fn as_bytes(&self) -> &[u8] {
        let mut ptr = core::ptr::null::<u8>();
        let mut len = 0;
        // SAFETY: ffi call forwarded to <https://cplusplus.com/reference/string/string/c_str/> and <https://cplusplus.com/reference/string/string/size/>
        unsafe {
            sdecay_sys::sdecay::std_string_bytes(
                self.ptr(),
                core::ptr::from_mut(&mut ptr).cast::<*const core::ffi::c_char>(),
                core::ptr::from_mut(&mut len),
            );
        };
        // SAFETY: pointer and length returned from call above must correctly represent string contents (according to the doc)
        unsafe { core::slice::from_raw_parts(ptr, len) }
    }

    #[expect(missing_docs)]
    #[inline]
    #[cfg(feature = "alloc")]
    pub fn as_str(&self) -> alloc::borrow::Cow<'_, str> {
        alloc::string::String::from_utf8_lossy(self.as_bytes())
    }

    #[expect(missing_docs)]
    #[inline]
    #[cfg(not(feature = "alloc"))]
    pub fn as_str(&self) -> &str {
        core::str::from_utf8(self.as_bytes())
            .expect("std::string should be valid utf8 to call `as_str` in no-alloc environment")
    }
}

impl Display for StdString {
    #[inline]
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        #[cfg(feature = "alloc")]
        {
            Display::fmt(&self.as_str(), f)
        }
        #[cfg(not(feature = "alloc"))]
        {
            write!(f, "{:#?}", self.as_cstr())
        }
    }
}

impl PartialEq for StdString {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        self.as_bytes() == other.as_bytes()
    }
}

impl Eq for StdString {}

macro_rules! impl_eq_as_bytes {
    ($t:ty) => {
        impl PartialEq<$t> for StdString {
            #[inline]
            fn eq(&self, other: &$t) -> bool {
                self.as_bytes() == other.as_bytes()
            }
        }

        impl PartialEq<StdString> for $t {
            #[inline]
            fn eq(&self, other: &StdString) -> bool {
                self.as_bytes() == other.as_bytes()
            }
        }

        impl PartialEq<&$t> for StdString {
            #[inline]
            fn eq(&self, other: &&$t) -> bool {
                self.as_bytes() == other.as_bytes()
            }
        }

        impl PartialEq<StdString> for &$t {
            #[inline]
            fn eq(&self, other: &StdString) -> bool {
                self.as_bytes() == other.as_bytes()
            }
        }
    };
}

impl_eq_as_bytes!(str);
#[cfg(feature = "alloc")]
impl_eq_as_bytes!(alloc::string::String);
#[cfg(feature = "alloc")]
impl_eq_as_bytes!(alloc::borrow::Cow<'_, str>);

impl PartialOrd for StdString {
    #[inline]
    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for StdString {
    #[inline]
    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
        self.as_bytes().cmp(other.as_bytes())
    }
}

impl Hash for StdString {
    #[inline]
    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
        self.as_bytes().hash(state);
    }
}

impl AsRef<CStr> for StdString {
    #[inline]
    fn as_ref(&self) -> &CStr {
        self.as_cstr()
    }
}

containers! {!self StdString as sdecay_sys::sdecay: std_string_from_cstr ->
    /// Allocates new `std::string` from `&`[`CStr`]
    from_cstr(cstr: impl AsRef<CStr>)
    (cstr.as_ref().as_ptr()) -> StdString
}
containers! {!self StdString as sdecay_sys::sdecay: std_string_from_bytes ->
    /// Allocates new `std::string` from `&`[`[u8]`]
    from_bytes(data: impl AsRef<[u8]>)
    (data.as_ref().as_ptr().cast(), data.as_ref().len()) -> StdString
}