Skip to main content

pyo3/conversions/std/
cstring.rs

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