Mapping constant to type in pattern matching


#1

In rust-xcb, I wrap FFI events into an Event<T> struct. Currently the only way to access underlying event data is to:

  1. match the event code against a constant provided by the X protocol.
  2. unsafely cast the event to the appropriate type using cast_event which does nothing more than calling std::transmute.

I’d like to map the constant to the correct event type at compile time, offering the user to not resort to any unsafe code.
Ideally I would like this mapping to be hidden to user and allow them to match directly against a casted reference, or even better to the destructured event.

Typical code of current situation:

// event: Event<xcb_generic_event_t>
let r = event.response_type() & !0x80;
match r {
    xcb::KEY_PRESS => {
        let key_press : &xcb::KeyPressEvent = unsafe {
            xcb::cast_event(&event)
        };
        println!("Key '{}' pressed", key_press.detail());
    },
    _ => {}
}

Ideal hypothetical code:

// event: EventMap<xcb::KEY_PRESS, xcb::KeyPressEvent>
match event.map() {
    xcb::KeyPressEvent{ detail } => {
        println!("Key '{}' pressed", detail);
    },
    _ => {}
}

The normal way would be to have an enum grouping all the event types and their fields, but this would require a runtime penalty as 2 matches would be necessary instead of 1 currently (one hidden in the lib to build the enum, and the one performed by the user)

Is this still science-fiction Rust or is something similar possible?


#2

I really don’t see how you can avoid having to two matches. Wouldn’t you just be matching by type the second time as you propose?


#3

Well, the obvious solution is the following:

enum SafeEvent {
    KeyPress { detail: u8 ... },
    // ...
}

/// matches against ev.response_type() and encapsulate
/// unsafe code to provide a safe event
fn translate<T>(ev: Event<T>) -> SafeEvent;

let ev: Event<T> = conn.wait_for_event();
match translate(ev) {                            // here is 2 matches
    SafeEvent::KeyPress { detail } => { ... },
    _ => {}
}

That’s perfectly sound, but comes with a runtime cost and I thought there was zero cost abstraction for this.
I was looking into a solution involving a trait that requires the (associated) constant, but I don’t know how to roll this up to the pattern match.

#![feature(associated_consts)]
trait EventCode {
    const CODE: i32;
}

impl EventCode for KeyPressEvent {
    const CODE: i32 = xcb::KEY_PRESS;
}

My motivation is that even if there is low possibility for mistake in the following code, a wrong cast can remain undetected for long.

xcb::KEY_PRESS => {
    let key_press : &xcb::KeyPressEvent = unsafe {
        xcb::cast_event(&event)
    };
    ...
},

#4

I guess the EventCode trait allows to easily assert of the correct event code in the cast_event function.
That’s more than a good start, so I will start with this!


#5

I understand your concern about 2 sequential matches, but consider whether this is really an optimization to worry about.

  1. If they are both inlined into the same code in immediate sequence (e.g. if your translate were marked with `#[inline(always)]), the optimizer should easily be able to collapse them.
  2. Even if they are not inlined, match statements of such types should be pretty efficiently implemented by the compiler using jump tables.

I’d go with a more ergonomic API, code some stuff, compile it, and and see whether this is really a concern. You can easily prototype and compare the asm on http://play.rust-lang.org and share the results and ask people why things are not optimizing as you were hoping.