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}