use core::{
ffi::CStr,
fmt::{Debug, Display},
hash::Hash,
};
use crate::{
containers, impl_moveable,
wrapper::{BindgenString, Wrapper},
};
#[repr(C)]
pub struct StdString {
inner: BindgenString,
_pin: core::marker::PhantomPinned,
_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) {
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 {
let ptr = unsafe { sdecay_sys::sdecay::std_string_cstr(self.ptr()) };
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;
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),
);
};
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 ->
from_cstr(cstr: impl AsRef<CStr>)
(cstr.as_ref().as_ptr()) -> StdString
}
containers! {!self StdString as sdecay_sys::sdecay: std_string_from_bytes ->
from_bytes(data: impl AsRef<[u8]>)
(data.as_ref().as_ptr().cast(), data.as_ref().len()) -> StdString
}