I'm working with a C API that returns usize as a status code. In my Rust wrapper function, I wish to return early if the C API returns a non-zero status code. So I was wondering if I could use the ? operator for this somehow. For example something like this:
fn rust_wrapper_func() -> Result<SomeType, ErrorType> {
c_func()?; //<--- This func returns usize and I'd like to have an early return here
// Do something else
//...
//...
}
Is this possible?
I've tried using the Try trait, but I can't implement it for usize due to orphan rules. Is there any way out?
I am afraid that your best bet will probably be to wrap every C API call in a macro that performs an early return if the return value is nonzero.
For example, you could do this (not tested):
macro_rules! exit_if_nonzero {
($faillible:expr) => {
let return_code: usize = $faillible;
if return_code != 0 {
// NOTE: May also use Err()? to benefit from Into conversion
return Err(/* generate output error from return_code */);
}
}
}
fn rust_wrapper_func() -> Result<SomeType, ErrorType> {
exit_if_nonzero!(c_func());
// Do something else
//...
//...
}
With this approach, you are tagging every integer from the C API as the potential error that it is, while leaving the other "normal" integers from your program alone. I think this is the sanest way to handle C's repurposing of integer return values for error-handling purpose.
The typical way out is to newtype the foreign type (usize in this case) and impl the trait for the newtype. So perhaps you can define a RetCode(usize) (or similar) wrapper and then work off that (i.e. impl Try for it).
Depending on how much unstable feature appetite you have, you can #[repr(transparent)] your wrapper struct and then declare the FFI fn as returning that (instead of the underlying type). That'll save you from needing to do any conversions/casts in the Rust code.
Nice. I used to to think repr(C) would achieve the same effect but turns out it doesn't as repr(C) merely affects the struct layout, as I understand, not how values are passed to and from functions.
Which just checks if a pointer is null (the is_null() method) and bails early with Nullable::NULL. This also updates the LAST_ERROR thread local (which contains the most recent failure::Error) with NullPointer.
You could do something similar by creating some sort of CTry trait and c_try() which lets you convert an error to a C-style result.