12
submitted 1 year ago* (last edited 1 year ago) by orangeboats@lemmy.world to c/rust@programming.dev

For context: I am trying to write a Rust wrapper over a C library.

Like many C libraries, most of its functions return an int. Positive return values are meaningful (provides information) and negative values are error codes.

To give an example, think of something like int get_items_from_record(const struct record *rec, struct item *items). A positive value indicates how many items were returned. -1 could mean ErrorA, -2 ErrorB, and so on.

Since this is Rust, I want to represent this kind of integer as Result<T, E>, e.g.:

enum LibError {
    A = -1,
    B = -2,
    // ....
}

// LibResult is ideally just represented as an integer.
type LibResult = Result<NonNegativeInteger, LibError>;

// Then I can pass LibResult values back to the C code as i32 trivially.

Is there a way/crate to do this?

you are viewing a single comment's thread
view the rest of the comments
[-] aloso@programming.dev 2 points 1 year ago* (last edited 1 year ago)

This is not possible, because Rust still stores a discriminant even when the enum values don't overlap.

As far as I can tell, the only situation where Rust doesn't store a discriminant is when either the Ok or Err variant is zero-sized, and the other variant has a niche. So, Result&lt;(), ErrorEnum> can be represented as an integer, but Result can not.

You can still use enums, and implement simple conversions like this:

#[repr(i8)]
pub enum Error {
    E1 = -1,
    E2 = -2,
    E3 = -3,
    E4 = -4,
}

#[repr(i8)]
pub enum Success {
    S0 = 0,
    S1 = 1,
    S2 = 2,
    S3 = 3,
}

pub type LibResult = Result;

pub fn number_to_result(value: i32) -> Option {
    match value {
        -4 ..= -1 => Some(Err(unsafe { std::mem::transmute(value as i8) })),
        0 ..= 3 => Some(Err(unsafe { std::mem::transmute(value as i8) })),
        _ => return None,
    }
}

pub fn result_to_number(res: LibResult) -> i32 {
    match res {
        Ok(value) => value as i32,
        Err(error) => error as i32,
    }
}

P.S. Sorry that the generics aren't displayed due to Lemmy's bad santiziation.

[-] RunAwayFrog@sh.itjust.works 3 points 1 year ago* (last edited 1 year ago)

Discriminant is irrelevant and you're not supposed to fuck with it.

And there is zero reason to use unsafe/transmute for this.

pub enum LibErr {
    ErrX,
    ErrY,
    ErrZ,
    Unknown(i32),
}

struct RetVal(i32);

impl From for Result {
    fn from(RetVal(ret_val): RetVal) -> Self {
        if ret_val &lt; 0 {
            match ret_val {
                -1 => Err(LibErr::ErrX),
                -2 => Err(LibErr::ErrY),
                -3 => Err(LibErr::ErrZ),
                unknown => Err(LibErr::Unknown(unknown)),
            }
        } else { Ok(ret_val) }
    }
}

// RetVal(call_function()).into()

And just to explicitly point out, your code's also better because of the use of the standard traits. It took me a while to get into the habit, but using what's already there is always a good idea.

load more comments (5 replies)
load more comments (5 replies)
this post was submitted on 29 Jul 2023
12 points (92.9% liked)

Rust

5913 readers
59 users here now

Welcome to the Rust community! This is a place to discuss about the Rust programming language.

Wormhole

!performance@programming.dev

Credits

  • The icon is a modified version of the official rust logo (changing the colors to a gradient and black background)

founded 1 year ago
MODERATORS