3

I have a bunch of FFI functions that I call using C. The caller expects 1 for success, or -1 on failure.

struct Error;

fn my_rust_function() -> Result<(), Error> {
    Ok(())
}

#[allow(non_snake_case)]
pub extern "C" fn Called_From_C() -> i32 {
    let result = my_rust_function();

    match result {
        Ok(_) => 1,
        Err(_) => -1,
    }
}

Is there a more idiomatic way of converting my Result<(), Error> into the 1 / -1 return code?

1
  • That's how I'd do it — it's clear what is being performed. You could do result.map(|_| 1).map_err(|_| -1).unwrap(), but then you're creating needless closures. Commented Aug 5, 2019 at 22:56

2 Answers 2

2

I'd create an extension trait:

trait FfiError {
    fn as_c_error(&self) -> i32;
}

impl<T, E> FfiError for Result<T, E> {
    fn as_c_error(&self) -> i32 {
        match self {
            Ok(_) => 1,
            Err(_) => -1,
        }
    }
}

Once it's brought into scope, you can call it like any other method:

pub extern "C" fn called_from_c() -> i32 {
    my_rust_function().as_c_error()
}

See also:

Sign up to request clarification or add additional context in comments.

Comments

2

You could use repr(transparent) to create a type where you could implement From and that still represent a i32, this allow to compile check that you transform your result correctly assuming you didn't have bug in your from() implementation so maybe add some unit tests.

type MyResult = Result<(), ()>;

fn my_rust_function() -> MyResult {
    Ok(())
}

#[repr(transparent)]
pub struct CResult {
    code: i32,
//    code: libc::c_int, // if your C lib expect a `c_int` and not a `i32`
}

impl From<MyResult> for CResult {
    fn from(result: MyResult) -> Self {
        let code = match result {
            Ok(_) => 1,
            Err(_) => -1,
        };
        Self { code }
    }
}

#[allow(non_snake_case)]
pub extern "C" fn Called_From_C() -> CResult {
    let result = my_rust_function();

    result.into()
}

You could also use enum with repr(i32):

#[repr(i32)]
pub enum CResult {
    NoError = 1,
    Error = -1,
}

impl From<MyResult> for CResult {
    fn from(result: MyResult) -> Self {
        match result {
            Ok(_) => CResult::NoError,
            Err(_) => CResult::Error,
        }
    }
}

In nightly, you could also implement Try:

#![feature(try_trait)]
use std::ops::Try;

type MyResult = Result<(), ()>;

fn my_rust_function() -> MyResult {
    Ok(())
}

#[repr(i32)]
pub enum CResult {
    NoError = 1,
    Error = -1,
}

impl From<MyResult> for CResult {
    fn from(result: MyResult) -> Self {
        match result {
            Ok(_) => CResult::NoError,
            Err(_) => CResult::Error,
        }
    }
}

impl From<CResult> for MyResult {
    fn from(cresult: CResult) -> Self {
        match cresult {
            CResult::NoError => Ok(()),
            CResult::Error => Err(()),
        }
    }
}

impl Try for CResult {
    type Ok = ();
    type Error = ();

    fn into_result(self) -> MyResult {
        self.into()
    }

    fn from_ok(_: <Self as Try>::Ok) -> Self {
        Self::NoError
    }

    fn from_error(_: <Self as Try>::Error) -> Self {
        Self::Error
    }
}

#[allow(non_snake_case)]
pub extern "C" fn Called_From_C() -> CResult {
    let _ = my_rust_function()?;

    CResult::NoError
}

Note: Be careful with the enumeration one, make sure your implementation is compatible. #[repr(libc::c_int)] is what we really want but I don't know any way to express this in Rust. So maybe a structure with repr(transparent) is more safe if the lib expect a c_int.

3 Comments

Why is #[repr(transparent)] necessary? It wouldn't affect how you'd write the code, and I'm fairly certain the compiler makes that optimization anyways.
@jhpratt Because it would be UB without github.com/rust-lang/rfcs/blob/master/text/…
Thanks for the detailed answer. I had not thought of creating an enum but it's a very neat solution. I marked @shepmaster response as the answer, because it is closest in spirit to what I'm trying to achieve.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.