pyo3/conversions/std/
cstring.rs

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