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?

7 points

I think you can implement Into for Result<…, LibError>. You can also implement the branching trait of Result so ? works. I’ve done it for booleans in the past as a learning experiment.

Unfortunately on mobile so I can’t link or copy paste code but that should give you some pointers to the right doc.

permalink
report
reply
2 points
*

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.

permalink
report
reply
3 points
*

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()
permalink
report
parent
reply
3 points

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.

permalink
report
parent
reply
2 points

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

It matters because the conversion between i32 and the Result is only “free” if they have the same layout (which they do not, because of the discriminant). So a more costly conversion method is required.

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

You are right, because the compiler is able to optimize your code quite well. However, if that optimization were to break at some point (as there is no guarantee that an optimization will continue to work in the future), it would become less efficient.

permalink
report
parent
reply
5 points

That seems like strong premature optimisation. Perhaps worth a note, but I’d presume the majority of people the majority of the time wouldn’t need to worry about that.

permalink
report
parent
reply
0 points

So what! Who cares if it’s free? Write first, profile and optimize later. Not everyone cares about whether the conversion is free. Simply matching and converting to the right integer is fast enough.

permalink
report
parent
reply
1 point

You’ve pretty much got it figured out. Create your result type and write an Impl for covering to/from integers. You can use From/Into to keep it idiomatic.

permalink
report
reply

Rust

!rust@programming.dev

Create post

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)

Community stats

  • 476

    Monthly active users

  • 815

    Posts

  • 3.7K

    Comments