I'm trying to build a Python package from Rust using PyO3 (version: 0.13.2). Right now I'm stuck trying to get the conversion working for enums. I have a simple enum like so:
#[derive(FromPyObject)]
#[derive(Copy,Clone,PartialEq,Eq)]
enum Direction {
Left,
Right,
Up,
Down
}
I added #[derive(FromPyObject)]
as per the documentation, however, I get the following error:
error: cannot derive FromPyObject for empty structs and variants --> src/main.rs:3:10 | 3 | #[derive(FromPyObject)] |
^^^^^^^^^^^^ | = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
In the example, all of the enum values have types associated with them. If this is the source of the error, is there any way around it so it'll work with the enum I have?
Thanks for any help.
SOLUTION
Here is the solution I ended up with. I'm new to Rust, so use it at your own risk. Thanks to Ahmed Mehrez from this question for providing the basis for the macro.
You'll need the following dependencies.
[dependencies]
num-traits = "0.2"
num-derive = "0.3"
The macro implements IntoPy and FromPyObject for the enum. It converts to/from an int. Plus, you can now iterate the enum!
use pyo3::prelude::*;
#[macro_use]
extern crate num_derive;
use num_traits::FromPrimitive;
// https://stackoverflow.com/questions/21371534/in-rust-is-there-a-way-to-iterate-through-the-values-of-an-enum
macro_rules! simple_enum {
($visibility:vis, $name:ident, $($member:tt),*) => {
#[derive(Copy,Clone)]
$visibility enum $name {$($member),*}
impl $name {
fn iterate() -> Vec<$name> {
vec![$($name::$member,) *]
}
}
impl IntoPy<PyObject> for $name {
fn into_py(self, py: Python) -> PyObject {
(self as u8).into_py(py)
}
}
impl FromPyObject<'_> for $name {
fn extract(ob: &'_ PyAny) -> PyResult<$name> {
let value: u8 = ob.extract().unwrap();
if let Some(val) = FromPrimitive::from_u8(value) {
for member in $name::iterate() {
if (member as u8) == val {
return Ok(member);
}
}
}
panic!("Invalid value ({}).", value);
}
}
};
($name:ident, $($member:tt),*) => {
simple_enum!(, $name, $($member),*)
};
}
// Example
simple_enum!(pub, Direction,
Left,
Right,
Up,
Down
);
From Python, you'll want to redefine your enum and use the value with your Rust module.
from enum import Enum
class Direction(Enum):
Left = 1
Right = 2
Up = 3
Down = 4
// Direction.Left.value