Skip to main content

pyo3/conversions/std/
cstring.rs

1use crate::exceptions::PyUnicodeDecodeError;
2#[cfg(feature = "experimental-inspect")]
3use crate::inspect::PyStaticExpr;
4#[allow(unused_imports, reason = "conditionally used")]
5use crate::platform::prelude::*;
6#[cfg(feature = "experimental-inspect")]
7use crate::type_object::PyTypeInfo;
8use crate::types::PyString;
9use crate::{Borrowed, Bound, FromPyObject, IntoPyObject, PyAny, PyErr, Python};
10use alloc::borrow::Cow;
11use alloc::ffi::CString;
12use core::ffi::CStr;
13#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
14use {
15    crate::{exceptions::PyValueError, ffi},
16    core::slice,
17};
18
19impl<'py> IntoPyObject<'py> for &CStr {
20    type Target = PyString;
21    type Output = Bound<'py, Self::Target>;
22    type Error = PyErr;
23
24    #[cfg(feature = "experimental-inspect")]
25    const OUTPUT_TYPE: PyStaticExpr = <&str>::OUTPUT_TYPE;
26
27    #[inline]
28    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
29        self.to_str()
30            .map_err(|e| PyUnicodeDecodeError::new_err_from_utf8(py, self.to_bytes(), e))?
31            .into_pyobject(py)
32            .map_err(|err| match err {})
33    }
34}
35
36impl<'py> IntoPyObject<'py> for CString {
37    type Target = PyString;
38    type Output = Bound<'py, Self::Target>;
39    type Error = PyErr;
40
41    #[cfg(feature = "experimental-inspect")]
42    const OUTPUT_TYPE: PyStaticExpr = <&CStr>::OUTPUT_TYPE;
43
44    #[inline]
45    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
46        (&*self).into_pyobject(py)
47    }
48}
49
50impl<'py> IntoPyObject<'py> for &CString {
51    type Target = PyString;
52    type Output = Bound<'py, Self::Target>;
53    type Error = PyErr;
54
55    #[cfg(feature = "experimental-inspect")]
56    const OUTPUT_TYPE: PyStaticExpr = <&CStr>::OUTPUT_TYPE;
57
58    #[inline]
59    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
60        (&**self).into_pyobject(py)
61    }
62}
63
64impl<'py> IntoPyObject<'py> for Cow<'_, CStr> {
65    type Target = PyString;
66    type Output = Bound<'py, Self::Target>;
67    type Error = PyErr;
68
69    #[cfg(feature = "experimental-inspect")]
70    const OUTPUT_TYPE: PyStaticExpr = <&CStr>::OUTPUT_TYPE;
71
72    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
73        (*self).into_pyobject(py)
74    }
75}
76
77impl<'py> IntoPyObject<'py> for &Cow<'_, CStr> {
78    type Target = PyString;
79    type Output = Bound<'py, Self::Target>;
80    type Error = PyErr;
81
82    #[cfg(feature = "experimental-inspect")]
83    const OUTPUT_TYPE: PyStaticExpr = <&CStr>::OUTPUT_TYPE;
84
85    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
86        (&**self).into_pyobject(py)
87    }
88}
89
90#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
91impl<'a> FromPyObject<'a, '_> for &'a CStr {
92    type Error = PyErr;
93
94    #[cfg(feature = "experimental-inspect")]
95    const INPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT;
96
97    fn extract(obj: Borrowed<'a, '_, PyAny>) -> Result<Self, Self::Error> {
98        let obj = obj.cast::<PyString>()?;
99        let mut size = 0;
100        // SAFETY: obj is a PyString so we can safely call PyUnicode_AsUTF8AndSize
101        let ptr = unsafe { ffi::PyUnicode_AsUTF8AndSize(obj.as_ptr(), &mut size) };
102
103        if ptr.is_null() {
104            return Err(PyErr::fetch(obj.py()));
105        }
106
107        // SAFETY: PyUnicode_AsUTF8AndSize always returns a NUL-terminated string but size does not
108        // include the NUL terminator. So we add 1 to the size to include it.
109        let slice = unsafe { slice::from_raw_parts(ptr.cast(), size as usize + 1) };
110
111        CStr::from_bytes_with_nul(slice).map_err(|err| PyValueError::new_err(err.to_string()))
112    }
113}
114
115impl<'a> FromPyObject<'a, '_> for Cow<'a, CStr> {
116    type Error = PyErr;
117
118    #[cfg(feature = "experimental-inspect")]
119    const INPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT;
120
121    fn extract(obj: Borrowed<'a, '_, PyAny>) -> Result<Self, Self::Error> {
122        #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
123        {
124            Ok(Cow::Borrowed(obj.extract::<&CStr>()?))
125        }
126
127        #[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
128        {
129            Ok(Cow::Owned(obj.extract::<CString>()?))
130        }
131    }
132}
133impl FromPyObject<'_, '_> for CString {
134    type Error = PyErr;
135
136    #[cfg(feature = "experimental-inspect")]
137    const INPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT;
138
139    fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result<Self, Self::Error> {
140        #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
141        {
142            Ok(obj.extract::<&CStr>()?.to_owned())
143        }
144
145        #[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
146        {
147            CString::new(&*obj.cast::<PyString>()?.to_cow()?).map_err(Into::into)
148        }
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155    use crate::types::string::PyStringMethods;
156    use crate::types::PyAnyMethods;
157    use crate::Python;
158
159    #[test]
160    fn test_into_pyobject() {
161        Python::attach(|py| {
162            let s = "Hello, Python!";
163            let cstr = CString::new(s).unwrap();
164
165            let py_string = cstr.as_c_str().into_pyobject(py).unwrap();
166            assert_eq!(py_string.to_cow().unwrap(), s);
167
168            let py_string = cstr.into_pyobject(py).unwrap();
169            assert_eq!(py_string.to_cow().unwrap(), s);
170        })
171    }
172
173    #[test]
174    fn test_extract_with_nul_error() {
175        Python::attach(|py| {
176            let s = "Hello\0Python";
177            let py_string = s.into_pyobject(py).unwrap();
178
179            #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
180            {
181                let err = py_string.extract::<&CStr>();
182                assert!(err.is_err());
183            }
184
185            let err = py_string.extract::<CString>();
186            assert!(err.is_err());
187        })
188    }
189
190    #[test]
191    fn test_extract_cstr_and_cstring() {
192        Python::attach(|py| {
193            let s = "Hello, world!";
194            let cstr = CString::new(s).unwrap();
195            let py_string = cstr.as_c_str().into_pyobject(py).unwrap();
196
197            #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
198            {
199                let extracted_cstr: &CStr = py_string.extract().unwrap();
200                assert_eq!(extracted_cstr.to_str().unwrap(), s);
201            }
202
203            let extracted_cstring: CString = py_string.extract().unwrap();
204            assert_eq!(extracted_cstring.to_str().unwrap(), s);
205        })
206    }
207
208    #[test]
209    fn test_cow_roundtrip() {
210        Python::attach(|py| {
211            let s = "Hello, world!";
212            let cstr = CString::new(s).unwrap();
213            let cow: Cow<'_, CStr> = Cow::Borrowed(cstr.as_c_str());
214
215            let py_string = cow.into_pyobject(py).unwrap();
216            assert_eq!(py_string.to_cow().unwrap(), s);
217
218            let roundtripped: Cow<'_, CStr> = py_string.extract().unwrap();
219            assert_eq!(roundtripped.as_ref(), cstr.as_c_str());
220        })
221    }
222}
⚠️ Internal Docs ⚠️ Not Public API 👉 Official Docs Here