2
votes

I'm working on the rust-efl project, which is a binding to the Enlightenment EFL C library.

There is a function that takes two C-style enums to configure the library. I want to restrict the second argument to a certain enum based on the value of the first argument.

I'll use the actual code for this example since it makes it a bit easier to understand the use case.

Example call:

elementary::policy_set(ElmPolicy::Quit, ElmPolicyQuit::LastWindowClosed as i32);

Library Rust code:

#[repr(C)]
pub enum ElmPolicy {
    /// under which circumstances the application should quit automatically. See ElmPolicyQuit
    Quit = 0,
    /// defines elm_exit() behaviour. See ElmPolicyExit
    Exit,
    /// defines how throttling should work. See ElmPolicyThrottle
    Throttle
}
#[repr(C)]
pub enum ElmPolicyQuit {
    /// never quit the application automatically
    None = 0,
    /// quit when the application's last window is closed
    LastWindowClosed,
    /// quit when the application's last window is hidden
    LastWindowHidden
}

pub fn policy_set(policy: ElmPolicy, value: i32) {

There is an enum similar to ElmPolicyQuit for each value of ElmPolicy. The second argument to policy_set should be of the corresponding enum type.

I would like to modify policy_set so that the caller does not have to cast the value to an i32 by defining a type for value. Ideally, I would like Rust to check that the second argument is of the correct type for the given policy argument.

Implementation Attempt

I'm new to Rust, so this may be way off, but this is my current attempt:

pub fn policy_set<P: ElmPolicy, V: ElmPolicyValue<P>>(policy: P, value: V) {
    unsafe { elm_policy_set(policy as c_int, value as c_int) }
}

trait ElmPolicyValue<P> {}
impl ElmPolicyValue<ElmPolicy::Quit> for ElmPolicyQuit {}

But I get this error:

src/elementary.rs:246:22: 246:31 error: ElmPolicy is not a trait [E0404] src/elementary.rs:246 pub fn policy_set>(policy: P, value: V) {

(arrow points to first argument's type)

I made a dummy trait for ElmPolicy, but then I get

src/elementary.rs:111:21: 111:36 error: found value elementary::ElmPolicy::Quit used as a type [E0248] src/elementary.rs:111 impl ElmPolicyValue for ElmPolicyQuit {}

So it seems like I can't use enums for generic types in this way. What is the correct way to implement this?

I suppose I don't need these to actually be enums. I just need the values to be convertible to a c_int that corresponds with the EFL library's C enum.

Also, I thought about a single argument instead.

elementary::policy_set(elementary::Policy::Quit::LastWindowClosed);

But nested C-like enums don't seem to work despite documentation I found and I'm uncertain about using #[repr(C)] with nested enums.

2

2 Answers

2
votes

Here's how I would do it: instead of defining ElmPolicy as an enum, I'd define it as a trait with an associated type that specifies the parameter type for the policy's value. Each policy would define a unit struct that implements ElmPolicy.

pub trait ElmPolicy {
    type Value;
}

pub struct ElmPolicyQuit;

impl ElmPolicy for ElmPolicyQuit {
    type Value = ElmPolicyQuitValue;
}

// TODO: ElmPolicyExit and ElmPolicyThrottle

#[repr(C)]
pub enum ElmPolicyQuitValue {
    /// never quit the application automatically
    None = 0,
    /// quit when the application's last window is closed
    LastWindowClosed,
    /// quit when the application's last window is hidden
    LastWindowHidden
}

pub fn policy_set<P: ElmPolicy>(policy: P, value: P::Value) {
    // TODO
}

fn main() {
    policy_set(ElmPolicyQuit, ElmPolicyQuitValue::LastWindowClosed);
    //policy_set(ElmPolicyQuit, ElmPolicyQuitValue::LastWindowClosed as i32); // does not compile
}

You can add methods or supertraits to ElmPolicy and contraints to ElmPolicy::Value as necessary. For example, you might want to add Into<i32> as a constraint on ElmPolicy::Value, so that you can use value.into() in policy_set to convert the value to an i32.

2
votes

I wouldn't try to solve both problems in one swoop. Create a straight-forward Rust binding to the C library that exposes the API as is (a *-sys package). Then provide a more idiomatic Rust API on top:

enum ElmPolicy {
    Quit(QuitDetail),
    Exit(ExitDetail),
    Throttle(ThrottleDetail),
}

enum QuitDetail {
    None,
    LastWindowClosed,
    LastWindowHidden,
}

Then you can create methods on ElmPolicy that produce a tuple of the C enums, or directly calls the appropriate C function, as desired.

Note that there is likely to be a bit of boilerplate as your enums in the *-sys crate will mirror those in the idiomatic API and you will want to convert between them. Macros may help reduce that.