Reputation: 5636
I tried to create FFI bindings to libmodbus, written in C. Here I stumble upon this function
modbus_set_error_recovery(ctx,
MODBUS_ERROR_RECOVERY_LINK |
MODBUS_ERROR_RECOVERY_PROTOCOL);
The second parameter is defined as
typedef enum
{
MODBUS_ERROR_RECOVERY_NONE = 0,
MODBUS_ERROR_RECOVERY_LINK = (1<<1),
MODBUS_ERROR_RECOVERY_PROTOCOL = (1<<2)
} modbus_error_recovery_mode;
My bindgen
-generated bindings are these:
#[repr(u32)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum modbus_error_recovery_mode {
MODBUS_ERROR_RECOVERY_NONE = 0,
MODBUS_ERROR_RECOVERY_LINK = 2,
MODBUS_ERROR_RECOVERY_PROTOCOL = 4,
}
and
extern "C" {
pub fn modbus_set_error_recovery(ctx: *mut modbus_t,
error_recovery:
modbus_error_recovery_mode)
-> ::std::os::raw::c_int;
}
My safe interface looks like this, so far:
pub fn set_error_recovery(&mut self, error_recovery_mode: ErrorRecoveryMode) -> Result<()> {
unsafe {
match ffi::modbus_set_error_recovery(self.ctx, error_recovery_mode.to_c()) {
-1 => bail!(Error::last_os_error()),
0 => Ok(()),
_ => panic!("libmodbus API incompatible response"),
}
}
}
and
use std::ops::BitOr;
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum ErrorRecoveryMode {
NONE = 0,
Link = 2,
Protocol = 4,
}
impl ErrorRecoveryMode {
pub fn to_c(self) -> ffi::modbus_error_recovery_mode {
match self {
NONE => ffi::modbus_error_recovery_mode::MODBUS_ERROR_RECOVERY_NONE,
Link => ffi::modbus_error_recovery_mode::MODBUS_ERROR_RECOVERY_LINK,
Protocol => ffi::modbus_error_recovery_mode::MODBUS_ERROR_RECOVERY_PROTOCOL,
}
}
}
impl BitOr for ErrorRecoveryMode {
type Output = Self;
fn bitor(self, rhs: ErrorRecoveryMode) -> ErrorRecoveryMode {
self | rhs
}
}
This triggered a stack overflow if I call set_error_recovery
like this
assert!(modbus.set_error_recovery(ErrorRecoveryMode::Link | ErrorRecoveryMode::Protocol).is_ok())
The error is
thread 'set_error_recovery' has overflowed its stack
fatal runtime error: stack overflow
Upvotes: 1
Views: 737
Reputation: 430711
As DK. mentioned:
enum
and Rust's enum
have different restrictions.Luckily, Bindgen understands bitflags. If you generate your headers while passing the bitfield-enum
flag or by using Builder::bitfield_enum
:
bindgen --bitfield-enum modbus_error_recovery_mode fake-modbus.h
Bindgen will generate constants for each C enum value, a newtype wrapper, and implementations of the Bit*
traits:
// Many implementation details removed
pub struct modbus_error_recovery_mode(pub ::std::os::raw::c_uint);
pub const modbus_error_recovery_mode_MODBUS_ERROR_RECOVERY_NONE: modbus_error_recovery_mode =
modbus_error_recovery_mode(0);
pub const modbus_error_recovery_mode_MODBUS_ERROR_RECOVERY_LINK: modbus_error_recovery_mode =
modbus_error_recovery_mode(2);
pub const modbus_error_recovery_mode_MODBUS_ERROR_RECOVERY_PROTOCOL: modbus_error_recovery_mode =
modbus_error_recovery_mode(4);
impl ::std::ops::BitOr<modbus_error_recovery_mode> for modbus_error_recovery_mode {}
impl ::std::ops::BitOrAssign for modbus_error_recovery_mode {}
impl ::std::ops::BitAnd<modbus_error_recovery_mode> for modbus_error_recovery_mode {}
impl ::std::ops::BitAndAssign for modbus_error_recovery_mode {}
extern "C" {
pub fn modbus_set_error_recovery(
ctx: *mut modbus_t,
error_recovery: modbus_error_recovery_mode,
) -> ::std::os::raw::c_int;
}
How do I expose the bindgen generated constants to the public
Of course, creating an idiomatic Rust API to non-Rust code is the hard part. I might try something like this:
#[derive(Debug)]
struct Modbus(*mut raw::modbus_t);
#[derive(Debug)]
struct Error;
#[derive(Debug, Copy, Clone)]
enum ErrorRecovery {
Link,
Protocol,
}
impl ErrorRecovery {
fn as_raw(&self) -> raw::modbus_error_recovery_mode {
use ErrorRecovery::*;
match *self {
Link => raw::modbus_error_recovery_mode_MODBUS_ERROR_RECOVERY_LINK,
Protocol => raw::modbus_error_recovery_mode_MODBUS_ERROR_RECOVERY_PROTOCOL,
}
}
}
impl Modbus {
fn set_error_recovery(&mut self, flags: Option<&[ErrorRecovery]>) -> Result<(), Error> {
let flag = flags.unwrap_or(&[]).iter().fold(
raw::modbus_error_recovery_mode_MODBUS_ERROR_RECOVERY_NONE,
|acc, v| acc | v.as_raw(),
);
let res = unsafe { raw::modbus_set_error_recovery(self.0, flag) };
Ok(()) // real error checking
}
}
Upvotes: 4
Reputation: 59005
The problem is that C's enum
and Rust's enum
are very different things. In particular, C allows an enum
to have absolutely any value whatsoever, whether or not that value corresponds to a variant.
Rust does not. Rust relies on enum
s only ever having a single value of a defined variant, or you run the risk of undefined behaviour.
What you have is not an enumeration (in the Rust sense), you have are bit flags, for which you want the bitflags
crate.
As for the stack overflow, that's just because you defined the BitOr
implementations in terms of itself; that code is unconditionally recursive.
Upvotes: 2