Rust FFI

A nice side effect of Outcome.Experimental’s excellent C support is that teaching Rust about Outcome’s result<T> becomes trivially easy. C and C++ results propagate losslessly into Rust Results, and the full power of the Outcome C API is available to Rust code for semantic equivalence comparison et al.

Here’s a quick snippet to get you started. This assumes that you have declared your C result using CXX_DECLARE_RESULT_SYSTEM(outcome, intptr_t) in order to produce a C result named “outcome” compatible with an erased system code C++ result:

// Rust representation of an Outcome.Experimental Result
pub type OutcomeCResult<T> = Result<T, cxx_status_code_system>;

unsafe impl Send for cxx_status_code_system {}
unsafe impl Sync for cxx_status_code_system {}

impl std::fmt::Display for cxx_status_code_system {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            String::from_utf8_lossy(unsafe {
                CStr::from_ptr(outcome_status_code_message(
                    self as *const _ as *const ::std::os::raw::c_void,
                ))
                .to_bytes()
            })
        )
    }
}

impl std::fmt::Debug for cxx_status_code_system {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            String::from_utf8_lossy(unsafe {
                CStr::from_ptr(outcome_status_code_message(
                    self as *const _ as *const ::std::os::raw::c_void,
                ))
                .to_bytes()
            })
        )
    }
}

// Tell Rust that this meets the `Error` trait
impl std::error::Error for cxx_status_code_system {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        None
    }
}

// Tell Rust how to make an `io::Result<T>` from a `OutcomeCResult<T>`
impl From<cxx_status_code_system> for std::io::Error {
    fn from(val: cxx_status_code_system) -> Self {
        // This is nasty, and we ought to figure out a better way of doing this
        for n in 1..200 {
            if unsafe {
                outcome_status_code_equal_generic(
                    &val as *const cxx_status_code_system as *const ::std::os::raw::c_void,
                    n,
                ) != 0
            } {
                return Self::new(Self::from_raw_os_error(n).kind(), val);
            }
        }
        Self::new(std::io::ErrorKind::Other, val)
    }
}

impl std::fmt::Debug for cxx_result_status_code_system_outcome {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if (self.flags & 1) == 1 {
            let v: OutcomeCResult<isize> = Ok(self.value);
            return v.fmt(f);
        } else {
            let v: OutcomeCResult<isize> = Err(self.error);
            return v.fmt(f);
        }
    }
}

pub fn to_result(res: cxx_result_status_code_system_outcome) -> OutcomeCResult<isize> {
    if (res.flags & 1) == 1 {
        return Ok(res.value);
    }
    Err(res.error)
}

pub fn success(val: isize) -> outcome_c_result {
    // You will need to export BOOST_OUTCOME_C_MAKE_RESULT_SYSTEM_SUCCESS(outcome, v) as
    // FFI outcome_c_make_success()
    unsafe { outcome_c_make_success(val) }
}

pub fn failure_from_errno(val: ::std::os::raw::c_int) -> outcome_c_result {
    // You will need to export BOOST_OUTCOME_C_MAKE_RESULT_SYSTEM_FAILURE_SYSTEM(outcome, v) as
    // FFI outcome_c_make_failure()
    unsafe { outcome_c_make_failure(val) }
}

Let’s say there is an FFI function like this:

unsafe extern "C" {
    pub fn file_read(
        arg1: *mut db,
        buffer: *mut u8,
        bytes: usize,
    ) -> outcome_c_result;
}

You can now do:

// Make a Rust Result equivalent to the Outcome Result
let res = to_result(unsafe { file_read(db, buffer, 256) });
// Asks Outcome for the message automatically
println!("Message: {}", res.err().unwrap());

You can use the standard Rust ? to TRY Rust Results, same as anywhere else in Rust.

As we taught Rust how to lossy map OutcomeCResult<T> into an io::Result<T>, you can also ? from an Outcome C Result function into an i/o Result function. Note that this is lossy, and you may not wish to allow that by removing the From trait definition above.

Easy as pie!