Say I have the following C++ Date class. Using pybind11 I can easily wrap it to be used in Python:
/* date.hpp */ class Date { public: int year, month, day; Date(int year, int month, int day); Date add_days(int N) const; // Returns new date with N days added bool is_weekend() const; // Returns true if date is Sat or Sun }; /* date_bindings.cpp */ #include <pybind11/pybind11.h> #include <date.hpp> namespace py = pybind11; PYBIND11_MODULE(date_py, m) { py::class_<Date>(m, "Date") .def(py::init<int, int, int>(), py::arg("year"), py::arg("month"), py::arg("day")) .def("add_days", &Date::add_days, py::arg("N")) .def("is_weekend", &Date::is_weekend) .def_readwrite("year", &Date::year) .def_readwrite("month", &Date::month) .def_readwrite("day", &Date::day); } Now, I want to do the same in Rust. So I implemented my Date class and then used PyO3 to wrap it. See code below.
I've noticed that the PyO3 bindings contain a lot of boilerplate, and hence they are much longer compared to pybind11:
- 14 lines with pybind11
- 54 lines with PyO3
Am I using PyO3 correctly? Are there ways to shorten the code to achieve a similar level of "conciseness" as is the case for pybind11?
edit: If there is a way to create bindings without the DatePy helper struct, I would actually prefer that.
/* date.rs */ #[derive(Debug)] pub struct Date { pub year: u16, pub month: u8, pub day: u8 } impl Date { pub const fn add_days(&self, N: u8) -> Self { // Simplified logic for demonstration purposes Date{year: self.year, month: self.month, day: self.day + N} } pub const fn is_weekend(&self) -> bool { // ... true } } /* date_bindings.rs */ use pyo3::prelude::*; use my_library::Date; #[pyclass(name = "Date")] struct DatePy { _date: Date } #[pymethods] impl DatePy { #[new] fn new(year: u16, month: u8, day: u8) -> Self { DatePy { _date: Date{year, month, day} } } fn add_days(&self, N: u8) -> PyResult<Self> { Ok(DatePy{_date: self._date.add_days(N)}) } fn is_weekend(&self) -> PyResult<bool> { Ok(self._date.is_weekend()) } #[getter] fn get_year(&self) -> PyResult<u16> { Ok(self._date.year) } #[getter] fn get_month(&self) -> PyResult<u8> { Ok(self._date.month) } #[getter] fn get_day(&self) -> PyResult<u8> { Ok(self._date.day) } #[setter] fn set_year(&mut self, year: u16) -> PyResult<()> { self._date.year = year; Ok(()) } #[setter] fn set_month(&mut self, month: u8) -> PyResult<()> { self._date.month = month; Ok(()) } #[setter] fn set_day(&mut self, day: u8) -> PyResult<()> { self._date.day = day; Ok(()) } } #[pymodule] fn date_py(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::<DatePy>()?; Ok(()) }
DatePyas a helper struct. If there is a way to create bindings without it, I am not opposed to it.#[pyclass]annotation toDateand put#[pyo3(get, set)]annotations on the member fields#[pyclass]annotation to my originalDateclass, then that means my core rust class & Python bindings will be merged into one? Is there no way to keep them independent & simply expose the class to Python? Similar to the pybind example?PyDateeither, so why do you want one for Rust but not for C++?