pyo3/conversions/std/
cstring.rs1use 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 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 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}