cpython/objects/object.rs
1// Copyright (c) 2015 Daniel Grunwald
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy of this
4// software and associated documentation files (the "Software"), to deal in the Software
5// without restriction, including without limitation the rights to use, copy, modify, merge,
6// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7// to whom the Software is furnished to do so, subject to the following conditions:
8//
9// The above copyright notice and this permission notice shall be included in all copies or
10// substantial portions of the Software.
11//
12// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
15// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
17// DEALINGS IN THE SOFTWARE.
18
19use std::{mem, ptr};
20
21use crate::err::PyResult;
22use crate::ffi;
23use crate::objects::PyType;
24use crate::python::{
25 Python, PythonObject, PythonObjectDowncastError, PythonObjectWithCheckedDowncast,
26 PythonObjectWithTypeObject,
27};
28
29/// Represents a reference to a Python object.
30///
31/// Python objects are reference counted.
32/// Calling `clone_ref()` on a `PyObject` will return a new reference to the same object
33/// (thus incrementing the reference count).
34/// The `Drop` implementation will automatically decrement the reference count.
35/// You can also call `release_ref()` to explicitly decrement the reference count.
36/// This is slightly faster than relying on automatic drop, because `release_ref`
37/// does not need to check whether the GIL needs to be acquired.
38///
39/// `PyObject` can be used with all Python objects, since all python types
40/// derive from `object`. This crate also contains other, more specific types
41/// that serve as references to Python objects (e.g. `PyTuple` for Python tuples, etc.).
42///
43/// You can convert from any Python object to `PyObject` by calling `as_object()` or `into_object()`
44/// from the [PythonObject trait](trait.PythonObject.html).
45/// In the other direction, you can call `cast_as()` or `cast_into()`
46/// on `PyObject` to convert to more specific object types.
47///
48/// Most of the interesting methods are provided by the [ObjectProtocol trait](trait.ObjectProtocol.html).
49#[repr(C)]
50pub struct PyObject {
51 // PyObject owns one reference to the *PyObject
52 // ptr is not null
53 ptr: ptr::NonNull<ffi::PyObject>,
54}
55
56// PyObject is thread-safe, because all operations on it require a Python<'p> token.
57unsafe impl Send for PyObject {}
58unsafe impl Sync for PyObject {}
59
60/// Dropping a `PyObject` decrements the reference count on the object by 1.
61impl Drop for PyObject {
62 fn drop(&mut self) {
63 let _gil_guard = Python::acquire_gil();
64 unsafe {
65 ffi::Py_DECREF(self.ptr.as_ptr());
66 }
67 }
68}
69
70impl PythonObject for PyObject {
71 #[inline]
72 fn as_object(&self) -> &PyObject {
73 self
74 }
75
76 #[inline]
77 fn into_object(self) -> PyObject {
78 self
79 }
80
81 #[inline]
82 unsafe fn unchecked_downcast_from(o: PyObject) -> PyObject {
83 o
84 }
85
86 #[inline]
87 unsafe fn unchecked_downcast_borrow_from(o: &PyObject) -> &PyObject {
88 o
89 }
90}
91
92impl PythonObjectWithCheckedDowncast for PyObject {
93 #[inline]
94 fn downcast_from(
95 _py: Python<'_>,
96 obj: PyObject,
97 ) -> Result<PyObject, PythonObjectDowncastError<'_>> {
98 Ok(obj)
99 }
100
101 #[inline]
102 fn downcast_borrow_from<'a, 'p>(
103 _py: Python<'p>,
104 obj: &'a PyObject,
105 ) -> Result<&'a PyObject, PythonObjectDowncastError<'p>> {
106 Ok(obj)
107 }
108}
109
110impl PythonObjectWithTypeObject for PyObject {
111 #[inline]
112 fn type_object(py: Python) -> PyType {
113 unsafe { PyType::from_type_ptr(py, &mut ffi::PyBaseObject_Type) }
114 }
115}
116
117impl PyObject {
118 /// Creates a PyObject instance for the given FFI pointer.
119 /// This moves ownership over the pointer into the PyObject.
120 /// Undefined behavior if the pointer is NULL or invalid.
121 #[inline]
122 pub unsafe fn from_owned_ptr(_py: Python, ptr: *mut ffi::PyObject) -> PyObject {
123 debug_assert!(!ptr.is_null() && ffi::Py_REFCNT(ptr) > 0);
124 PyObject {
125 ptr: ptr::NonNull::new_unchecked(ptr),
126 }
127 }
128
129 /// Creates a PyObject instance for the given FFI pointer.
130 /// Calls Py_INCREF() on the ptr.
131 /// Undefined behavior if the pointer is NULL or invalid.
132 #[inline]
133 pub unsafe fn from_borrowed_ptr(_py: Python, ptr: *mut ffi::PyObject) -> PyObject {
134 debug_assert!(!ptr.is_null() && ffi::Py_REFCNT(ptr) > 0);
135 ffi::Py_INCREF(ptr);
136 PyObject {
137 ptr: ptr::NonNull::new_unchecked(ptr),
138 }
139 }
140
141 /// Creates a PyObject instance for the given FFI pointer.
142 /// This moves ownership over the pointer into the PyObject.
143 /// Returns None for null pointers; undefined behavior if the pointer is invalid.
144 #[inline]
145 pub unsafe fn from_owned_ptr_opt(py: Python, ptr: *mut ffi::PyObject) -> Option<PyObject> {
146 if ptr.is_null() {
147 None
148 } else {
149 Some(PyObject::from_owned_ptr(py, ptr))
150 }
151 }
152
153 /// Returns None for null pointers; undefined behavior if the pointer is invalid.
154 #[inline]
155 pub unsafe fn from_borrowed_ptr_opt(py: Python, ptr: *mut ffi::PyObject) -> Option<PyObject> {
156 if ptr.is_null() {
157 None
158 } else {
159 Some(PyObject::from_borrowed_ptr(py, ptr))
160 }
161 }
162
163 /// Gets the underlying FFI pointer.
164 /// Returns a borrowed pointer.
165 #[inline]
166 pub fn as_ptr(&self) -> *mut ffi::PyObject {
167 self.ptr.as_ptr()
168 }
169
170 /// Gets the underlying FFI pointer.
171 /// Consumes `self` without calling `Py_DECREF()`, thus returning an owned pointer.
172 #[inline]
173 #[must_use]
174 pub fn steal_ptr(self) -> *mut ffi::PyObject {
175 let ptr = self.as_ptr();
176 mem::forget(self);
177 ptr
178 }
179
180 /// Transmutes an FFI pointer to `&PyObject`.
181 /// Undefined behavior if the pointer is NULL or invalid.
182 #[inline]
183 pub unsafe fn borrow_from_ptr(ptr: &*mut ffi::PyObject) -> &PyObject {
184 debug_assert!(!ptr.is_null());
185 mem::transmute(ptr)
186 }
187
188 /// Transmutes a slice of owned FFI pointers to `&[PyObject]`.
189 /// Undefined behavior if any pointer in the slice is NULL or invalid.
190 #[inline]
191 pub unsafe fn borrow_from_owned_ptr_slice(ptr: &[*mut ffi::PyObject]) -> &[PyObject] {
192 mem::transmute(ptr)
193 }
194
195 /// Gets the reference count of this Python object.
196 #[inline]
197 pub fn get_refcnt(&self, _py: Python) -> usize {
198 unsafe { ffi::Py_REFCNT(self.as_ptr()) as usize }
199 }
200
201 /// Gets the Python type object for this object's type.
202 pub fn get_type(&self, py: Python) -> PyType {
203 unsafe { PyType::from_type_ptr(py, (*self.as_ptr()).ob_type) }
204 }
205
206 /// Casts the PyObject to a concrete Python object type.
207 /// Causes undefined behavior if the object is not of the expected type.
208 /// This is a wrapper function around `PythonObject::unchecked_downcast_from()`.
209 #[inline]
210 pub unsafe fn unchecked_cast_into<T>(self) -> T
211 where
212 T: PythonObject,
213 {
214 PythonObject::unchecked_downcast_from(self)
215 }
216
217 /// Casts the PyObject to a concrete Python object type.
218 /// Fails with `PythonObjectDowncastError` if the object is not of the expected type.
219 /// This is a wrapper function around `PythonObjectWithCheckedDowncast::downcast_from()`.
220 #[inline]
221 pub fn cast_into<T>(self, py: Python<'_>) -> Result<T, PythonObjectDowncastError<'_>>
222 where
223 T: PythonObjectWithCheckedDowncast,
224 {
225 PythonObjectWithCheckedDowncast::downcast_from(py, self)
226 }
227
228 /// Casts the PyObject to a concrete Python object type.
229 /// Causes undefined behavior if the object is not of the expected type.
230 /// This is a wrapper function around `PythonObject::unchecked_downcast_borrow_from()`.
231 #[inline]
232 pub unsafe fn unchecked_cast_as<T>(&self) -> &T
233 where
234 T: PythonObject,
235 {
236 PythonObject::unchecked_downcast_borrow_from(self)
237 }
238
239 /// Casts the PyObject to a concrete Python object type.
240 /// Fails with `PythonObjectDowncastError` if the object is not of the expected type.
241 /// This is a wrapper function around `PythonObjectWithCheckedDowncast::downcast_borrow_from()`.
242 #[inline]
243 pub fn cast_as<'s, 'p, T>(
244 &'s self,
245 py: Python<'p>,
246 ) -> Result<&'s T, PythonObjectDowncastError<'p>>
247 where
248 T: PythonObjectWithCheckedDowncast,
249 {
250 PythonObjectWithCheckedDowncast::downcast_borrow_from(py, self)
251 }
252
253 /// Extracts some type from the Python object.
254 /// This is a wrapper function around `FromPyObject::from_py_object()`.
255 #[inline]
256 pub fn extract<'a, T>(&'a self, py: Python) -> PyResult<T>
257 where
258 T: crate::conversion::FromPyObject<'a>,
259 {
260 crate::conversion::FromPyObject::extract(py, self)
261 }
262
263 /// True if this is None in Python.
264 #[inline]
265 pub fn is_none(&self, _py: Python) -> bool {
266 self.as_ptr() == unsafe { ffi::Py_None() }
267 }
268}
269
270/// PyObject implements the `==` operator using reference equality:
271/// `obj1 == obj2` in rust is equivalent to `obj1 is obj2` in Python.
272impl PartialEq for PyObject {
273 #[inline]
274 fn eq(&self, o: &PyObject) -> bool {
275 self.as_ptr() == o.as_ptr()
276 }
277}
278
279/// PyObject implements the `==` operator using reference equality:
280/// `obj1 == obj2` in rust is equivalent to `obj1 is obj2` in Python.
281impl Eq for PyObject {}
282
283#[test]
284fn test_sizeof() {
285 // should be a static_assert, but size_of is not a compile-time const
286 // these are necessary for the transmutes in this module
287 assert_eq!(
288 mem::size_of::<PyObject>(),
289 mem::size_of::<*mut ffi::PyObject>()
290 );
291 assert_eq!(
292 mem::size_of::<PyType>(),
293 mem::size_of::<*mut ffi::PyTypeObject>()
294 );
295}