Using the Union Types Generated by Bindgen

NOTE: Rust 1.19 stabilized the union type (see Rust issue #32836).

You can pass the --rust-target option to tell bindgen to target a specific version of Rust. By default, bindgen will target the latest stable Rust. The --rust-target option accepts a specific stable version (such as “1.0” or “1.19”) or “nightly”.

NOTE: The --unstable-rust option is deprecated; use --rust-target nightly instead.

In general, most interactions with unions (either reading or writing) are unsafe, meaning you must surround union accesses in an unsafe {} block.

For this discussion, we will use the following C type definitions:

typedef struct {
    int32_t a;
    int32_t b;
} alpha_t;

typedef struct {
    uint32_t c;
    uint16_t d;
    uint16_t e;
    uint8_t  f;
} beta_t;

typedef union {
    alpha_t alfa;
    beta_t  bravo;
} greek_t;

Relevant Bindgen Options

Library

Command Line

  • --rust-target
  • --with-derive-default

Which union type will Bindgen generate?

Bindgen can emit one of two Rust types that correspond to C unions:

  • Rust's union builtin (only available in Rust >= 1.19, including nightly)
  • Bindgen's BindgenUnion (available for all Rust targets)

Bindgen uses the following logic to determine which Rust union type to emit:

  • If the Rust target is >= 1.19 (including nightly) AND each field of the union can derive Copy, then generate a union builtin.
  • Otherwise, generate a BindgenUnion.

Using the union builtin

When using the union builtin type, there are two choices for initialization:

  1. Zero
  2. With a specific variant
mod bindings_builtin_union;

fn union_builtin() {
    // Initalize the union to zero
    let x = bindings_builtin_union::greek_t::default();

    // If `--with-derive-default` option is not used, the following may be used
    //   to initalize the union to zero:
    let x = unsafe { std::mem::zeroed::<bindings_builtin_union::greek_t>() };

    // Or, it is possible to initialize exactly one variant of the enum:
    let x = bindings_builtin_union::greek_t {
        alfa: bindings_builtin_union::alpha_t {
            a: 1,
            b: -1,
        },
    };

    unsafe {
        println!("{:?}", z.alfa);  // alpha_t { a: 1, b: -1 }
        println!("{:?}", z.bravo); // beta_t { c: 1, d: 65535, e: 65535, f: 127 }
    }
}

Using the BindgenUnion type

If the target Rust version does not support the new union type or there is a field that cannot derive Copy, then bindgen will provide union-like access to a struct.

Interacting with these unions is slightly different than the new union types. You must access union variants through a reference.

mod bindings;

fn bindgenunion() {
    // `default()` or `zeroed()` may still be used with Bindgen's Union types
    let mut x = bindings::greek_t::default();

    // This will not work:
    // let x = bindings::greek_t {
    //     alfa: bindings::alpha_t {
    //         a: 1,
    //         b: -1,
    //     },
    // };

    // Instead, access the field through `.as_ref()` and `.as_mut()` helpers:
    unsafe {
        *x.alfa.as_mut() = bindings::alpha_t {
            a: 1,
            b: -1,
        };

        println!("{:?}", x.alfa.as_ref());  // alpha_t { a: 1, b: -1 }
        println!("{:?}", x.bravo.as_ref()); // beta_t { c: 1, d: 65535, e: 65535, f: 0 }
    }

If you attempt to access a BindgenUnion field directly, you will see errors like this:

error[E0308]: mismatched types
  --> src/main.rs:44:15
   |
44 |           alfa: bindings::alpha_t {
   |  _______________^
45 | |             a: 1,
46 | |             b: -1,
47 | |         },
   | |_________^ expected struct `bindings::__BindgenUnionField`, found struct `bindings::alpha_t`
   |
   = note: expected type `bindings::__BindgenUnionField<bindings::alpha_t>`
              found type `bindings::alpha_t`