Option and Result

This chapter will cover the EzffiOption and EzffiResult wrappers. They're the C-types for the already-known Rust-types Option<R> and Result<T, E>, and are included in the ezffi/ezffi.h header.

ezffi ships with macros that allow the user to extract T from an Option and a Result. Don't be stupid: check if the Result is Ok and the Option is Some before unwrapping. Their definitions are:

#define EZFFI_OPTION_UNWRAP(Type, opt_ptr) ({ Type __ez_v; ezffi_option_unwrap((opt_ptr), &__ez_v); __ez_v; })
#define EZFFI_RESULT_UNWRAP(Type, res_ptr) ({ Type __ez_v; ezffi_result_unwrap((res_ptr), &__ez_v); __ez_v; })

and their usage is:

T value = EZFFI_OPTION_UNWRAP(T, opt_ptr);
T value = EZFFI_RESULT_UNWRAP(T, res_ptr);

I also provide the following functions:

// Helpers that allow the user to easily check if the wrappers
// contain any value.
bool ezffi_option_is_some(const struct EzffiOption *o);
bool ezffi_result_is_ok(const struct EzffiResult *o);

// In the case of `Result` I have this function to extract the code.
int32_t ezffi_result_error_code(const struct EzffiResult *o);

// You can also get a pointer to the error message. NULL if there
// is no error. This lives as long as EzffiResult lives.
const char *ezffi_result_error_message(const struct EzffiResult *o);

// These functions can be used to extract the generic type, but I
// recommend the macros, they're easier to read and use these
// functions internally.
// 
// This functions also free the structs allocated data, so you don't need
// to call the free function after using them or the macros
void ezffi_option_unwrap(const struct EzffiOption *o, void *out);
void ezffi_result_unwrap(const struct EzffiResult *o, void *out);

// Call this function to free the allocated memory in case you didn't 
// unwrap and want to ignore the returned value
void ezffi_option_free(const struct EzffiOption *o);
void ezffi_result_free(const struct EzffiResult *o);

To be able to use the EzffiResult struct, the Error type your Rust Result contains must implement From<&Error> for EzffiErrorezffi calls it on a borrow when constructing the wrapper, so no clone or extra allocation ever happens on the error side. You can use as many Error types as you want but always ensure each of them has a different error code. An example would be:

pub enum Error {
    IOError,
    SqlxError,
    ReqwestError,
}

impl From<&Error> for EzffiError {
    fn from(e: &Error) -> EzffiError {
        let (code, msg) = match e {
            Error::IOError => (1, "io error"),
            Error::SqlxError => (2, "sqlx error"),
            Error::ReqwestError => (3, "reqwest error"),
        };

        EzffiError::new(code, msg.to_string())
    }
}

By-ref shapes

Exported functions can take and return &Option<T>, &mut Option<T>, &Result<T, E> and &mut Result<T, E>. The C-side type is the same EzffiOption / EzffiResult, so no new headers or macros, the difference lives only in who owns the inner payload:

  • A wrapper built from an owned Rust value carries a heap-allocated box: ezffi_*_free releases it, ezffi_*_unwrap consumes it
  • A wrapper built from a borrow points at the Rust-side memory directly: it allocates nothing for the container, ezffi_*_free is effectively a no-op on the inner (it still releases the precomputed error message for the Err case), and ezffi_*_unwrap reads through the borrow without consuming it

Either way you call the same C functions; the wrapper carries a small vtable that picks the right path.