safa_abi/ffi/
str.rs

1use crate::{
2    errors::IntoErr,
3    ffi::{
4        NotZeroable,
5        slice::{InvalidSliceError, Slice},
6    },
7};
8
9#[derive(Debug, Clone, Copy)]
10/// Represents an FFI-safe alternative to [&str]
11///
12/// Has the same requirements as &[`str`], however these requirements may not be met if passed from a foreign callsite, so additional checks may be necessary.
13#[repr(transparent)]
14pub struct Str(Slice<u8>);
15
16/// An error that occurs when attempting to convert a [`Str`] to a &[`str`].
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum InvalidStrError {
19    InvalidSliceError(InvalidSliceError),
20    Utf8Error,
21}
22
23impl From<InvalidSliceError> for InvalidStrError {
24    fn from(value: InvalidSliceError) -> Self {
25        Self::InvalidSliceError(value)
26    }
27}
28
29impl IntoErr for InvalidStrError {
30    fn into_err(self) -> crate::errors::ErrorStatus {
31        match self {
32            Self::InvalidSliceError(e) => e.into_err(),
33            Self::Utf8Error => crate::errors::ErrorStatus::InvalidStr,
34        }
35    }
36}
37
38impl Str {
39    /// Creates a new [`Str`] from a string slice.
40    pub const fn from_str(s: &str) -> Self {
41        Self(Slice::from_slice(s.as_bytes()))
42    }
43
44    pub const fn as_bytes(&self) -> &Slice<u8> {
45        &self.0
46    }
47
48    pub const fn as_ptr(&self) -> *const u8 {
49        self.0.as_ptr()
50    }
51
52    pub const fn len(&self) -> usize {
53        self.0.len()
54    }
55
56    /// Attempts to convert [`Str`] into a mutable string slice &[str].
57    #[inline]
58    pub unsafe fn try_as_str_mut_custom<'a>(
59        &self,
60        custom_validate: impl FnOnce(*const ()) -> bool,
61    ) -> Result<&'a mut str, InvalidStrError> {
62        unsafe {
63            let byte_slice = self
64                .0
65                .try_as_slice_mut_custom(custom_validate)
66                .map_err(|e| InvalidStrError::InvalidSliceError(e))?;
67            core::str::from_utf8_mut(byte_slice).map_err(|_| InvalidStrError::Utf8Error)
68        }
69    }
70    /// Attempts to convert [`Str`] into a string slice &[`str`].
71    #[inline]
72    pub unsafe fn try_as_str_custom<'a>(
73        &self,
74        custom_validate: impl FnOnce(*const ()) -> bool,
75    ) -> Result<&'a str, InvalidStrError> {
76        unsafe {
77            match self.try_as_str_mut_custom(custom_validate) {
78                Ok(str) => Ok(str),
79                Err(e) => Err(e),
80            }
81        }
82    }
83
84    /// Attempts to convert [`Str`] into a string slice &[`str`].
85    #[inline]
86    pub unsafe fn try_as_str<'a>(&self) -> Result<&'a str, InvalidStrError> {
87        unsafe {
88            match self.try_as_str_custom(|_| true) {
89                Ok(str) => Ok(str),
90                Err(err) => Err(err),
91            }
92        }
93    }
94
95    /// Attempts to convert [`Str`] into a mutable string slice &mut [`str`].
96    #[inline]
97    pub unsafe fn try_as_str_mut<'a>(&self) -> Result<&'a mut str, InvalidStrError> {
98        unsafe {
99            match self.try_as_str_mut_custom(|_| true) {
100                Ok(str) => Ok(str),
101                Err(err) => Err(err),
102            }
103        }
104    }
105}
106
107impl Slice<Str> {
108    /// Converts a mutable slice of string slices [`*mut str`] into an FFI safe [`Slice`] of [`Str`]s.
109    ///
110    /// # Safety
111    ///
112    /// The given slice will be unsafely reused, for now the data will be left unchanged in the current rust version because the layout of [Str] is the same as &[str],
113    /// However since the layout of slices isn't guaranteed yet by rust, this function may change the given buffer in the future.
114    ///
115    /// this should be solved if this [RFC](https://github.com/rust-lang/rfcs/pull/3775) got accepted
116    #[inline]
117    pub const unsafe fn from_str_slices_mut(slices: *mut [*mut str]) -> Self {
118        let old_slices = unsafe { &mut *slices };
119        let raw_slices = unsafe { &mut *(slices as *mut [Str]) };
120
121        let mut i = 0;
122        while i < old_slices.len() {
123            let slice = old_slices[i];
124            raw_slices[i] = unsafe { Str::from_str(&*slice) };
125            i += 1;
126        }
127
128        Slice::from_slice(raw_slices)
129    }
130
131    /// Attempts to convert an FFI [Slice] of [`Str`]s into a rust slice of str slices *mut [*mut [`str`]].
132    /// given an FFI [Slice] of [`Str`]s
133    ///
134    /// # Safety
135    ///
136    /// The given FFI slice will be unsafely reused, for now the data will be left unchanged in the current rust version because the layout of [Str] is the same as &[str],
137    /// However since the layout of slices isn't guaranteed yet by rust, this function may change the given buffer in the future, or by some obscure optimizations.
138    ///
139    /// this should be solved if this [RFC](https://github.com/rust-lang/rfcs/pull/3775) got accepted
140    #[inline]
141    pub unsafe fn try_into_str_slices_mut<'a>(
142        self,
143        custom_validate: impl Fn(*const ()) -> bool,
144    ) -> Result<*mut [&'a str], InvalidStrError> {
145        let root = unsafe { self.try_as_slice_mut_custom(&custom_validate)? };
146        let root_ptr = root as *mut _;
147        let results = unsafe { &mut *(root_ptr as *mut [&'a str]) };
148
149        let mut i = 0;
150        while i < results.len() {
151            let slice = &root[i];
152            results[i] = unsafe { slice.try_as_str_custom(&custom_validate)? };
153            i += 1;
154        }
155
156        Ok(results)
157    }
158}
159
160impl NotZeroable for Str {
161    #[inline(always)]
162    fn is_zero(&self) -> bool {
163        self.0.is_zero()
164    }
165}