pyo3/
pyclass.rs

1//! `PyClass` and related traits.
2use crate::{ffi, impl_::pyclass::PyClassImpl, PyTypeInfo};
3use std::{cmp::Ordering, os::raw::c_int};
4
5mod create_type_object;
6mod gc;
7mod guard;
8
9pub(crate) use self::create_type_object::{create_type_object, PyClassTypeObject};
10
11pub use self::gc::{PyTraverseError, PyVisit};
12pub use self::guard::{PyClassGuard, PyClassGuardMap, PyClassGuardMut};
13
14/// Types that can be used as Python classes.
15///
16/// The `#[pyclass]` attribute implements this trait for your Rust struct -
17/// you shouldn't implement this trait directly.
18pub trait PyClass: PyTypeInfo + PyClassImpl {
19    /// Whether the pyclass is frozen.
20    ///
21    /// This can be enabled via `#[pyclass(frozen)]`.
22    type Frozen: Frozen;
23}
24
25/// Operators for the `__richcmp__` method
26#[derive(Debug, Clone, Copy)]
27pub enum CompareOp {
28    /// The *less than* operator.
29    Lt = ffi::Py_LT as isize,
30    /// The *less than or equal to* operator.
31    Le = ffi::Py_LE as isize,
32    /// The equality operator.
33    Eq = ffi::Py_EQ as isize,
34    /// The *not equal to* operator.
35    Ne = ffi::Py_NE as isize,
36    /// The *greater than* operator.
37    Gt = ffi::Py_GT as isize,
38    /// The *greater than or equal to* operator.
39    Ge = ffi::Py_GE as isize,
40}
41
42impl CompareOp {
43    /// Conversion from the C enum.
44    pub fn from_raw(op: c_int) -> Option<Self> {
45        match op {
46            ffi::Py_LT => Some(CompareOp::Lt),
47            ffi::Py_LE => Some(CompareOp::Le),
48            ffi::Py_EQ => Some(CompareOp::Eq),
49            ffi::Py_NE => Some(CompareOp::Ne),
50            ffi::Py_GT => Some(CompareOp::Gt),
51            ffi::Py_GE => Some(CompareOp::Ge),
52            _ => None,
53        }
54    }
55
56    /// Returns if a Rust [`std::cmp::Ordering`] matches this ordering query.
57    ///
58    /// Usage example:
59    ///
60    /// ```rust,no_run
61    /// # use pyo3::prelude::*;
62    /// # use pyo3::class::basic::CompareOp;
63    ///
64    /// #[pyclass]
65    /// struct Size {
66    ///     size: usize,
67    /// }
68    ///
69    /// #[pymethods]
70    /// impl Size {
71    ///     fn __richcmp__(&self, other: &Size, op: CompareOp) -> bool {
72    ///         op.matches(self.size.cmp(&other.size))
73    ///     }
74    /// }
75    /// ```
76    pub fn matches(&self, result: Ordering) -> bool {
77        match self {
78            CompareOp::Eq => result == Ordering::Equal,
79            CompareOp::Ne => result != Ordering::Equal,
80            CompareOp::Lt => result == Ordering::Less,
81            CompareOp::Le => result != Ordering::Greater,
82            CompareOp::Gt => result == Ordering::Greater,
83            CompareOp::Ge => result != Ordering::Less,
84        }
85    }
86}
87
88/// A workaround for [associated const equality](https://github.com/rust-lang/rust/issues/92827).
89///
90/// This serves to have True / False values in the [`PyClass`] trait's `Frozen` type.
91#[doc(hidden)]
92pub mod boolean_struct {
93    pub(crate) mod private {
94        use super::*;
95
96        /// A way to "seal" the boolean traits.
97        pub trait Boolean {
98            const VALUE: bool;
99        }
100
101        impl Boolean for True {
102            const VALUE: bool = true;
103        }
104        impl Boolean for False {
105            const VALUE: bool = false;
106        }
107    }
108
109    pub struct True(());
110    pub struct False(());
111}
112
113/// A trait which is used to describe whether a `#[pyclass]` is frozen.
114#[doc(hidden)]
115pub trait Frozen: boolean_struct::private::Boolean {}
116
117impl Frozen for boolean_struct::True {}
118impl Frozen for boolean_struct::False {}
119
120mod tests {
121    #[test]
122    fn test_compare_op_matches() {
123        use super::CompareOp;
124        use std::cmp::Ordering;
125
126        assert!(CompareOp::Eq.matches(Ordering::Equal));
127        assert!(CompareOp::Ne.matches(Ordering::Less));
128        assert!(CompareOp::Ge.matches(Ordering::Greater));
129        assert!(CompareOp::Gt.matches(Ordering::Greater));
130        assert!(CompareOp::Le.matches(Ordering::Equal));
131        assert!(CompareOp::Lt.matches(Ordering::Less));
132    }
133}