Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Resolve union structure in Rust FFI

I have problem with resolving c-union structure XEvent.

I'm experimenting with Xlib and X Record Extension in Rust. I'm generate ffi-bindings with rust-bindgen. All code hosted on github alxkolm/rust-xlib-record.

Trouble happen on line src/main.rs:106 when I try extract data from XEvent structure.


let key_event: *mut xlib::XKeyEvent = event.xkey();
println!("KeyPress {}", (*key_event).keycode); // this always print 128 on any key

My program listen key events and print out keycode. But it is always 128 on any key I press. I think this wrong conversion from C union type to Rust type.

Definition of XEvent starts here src/xlib.rs:1143. It's the c-union. Original C definition here.

Code from GitHub can be run by cargo run command. It's compile without errors.

What I do wrong?

like image 568
alxkolm Avatar asked Sep 30 '22 11:09

alxkolm


1 Answers

Beware that rustbindgen generates binding to C union with as much safety as in C; as a result, when calling:

event.xkey(); // gets the C union 'xkey' field

There is no runtime check that xkey is the field currently containing a value.

This is because since C does not have tagged union (ie, the union knowing which field is currently in use), developers came up with various ways of encoding this information (*), the two that I know of being:

  • an external supplier; typically another field of the structure right before the union
  • the first field of each of the structures in the union

Here, you are in the latter case int type; is the first field of the union and each nested structure starts with int _type; to denote this. As a result, you need a two-steps approach:

  1. consult type()
  2. depending on the value, call the correct reinterpretation

The mapping from the value of type to the actual field being used should be part of the documentation of the C library, hopefully.

I invite you to come up with a wrapper around this low-level union that will make it safer to retrieve the result. At the very least, you could check it is the right type in the accessor; the full approach being to come up with a Rust enum that would wrap proxies to all the fields and allow pattern-matching.

(*) and actually sometimes disregard it altogether, for example in C99 to reinterpret a float as an int a union { float f; int i; } can be used.

like image 157
Matthieu M. Avatar answered Oct 03 '22 02:10

Matthieu M.