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
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.