Enum and Union

Beyond interfaces and parcelables, AIDL has enums — named integer constants — and unions — a value that is one of several types at a time. Both map onto Rust, though the enum mapping is not the one you would guess.

Enum Types

AIDL enums are backed by a specific integer type, declared using the @Backing annotation. Unlike Rust's native enums, AIDL enums map to newtype structs wrapping the backing integer. This design preserves wire compatibility and allows values outside the defined set, which is important for forward compatibility in IPC.

Defining Enums in AIDL

The @Backing(type=...) annotation specifies the underlying integer type. It is optional — when omitted, the backing type defaults to byte. Here are examples for each supported backing type:

Byte-backed enum:

@Backing(type="byte")
enum ByteEnum {
    FOO = 1,
    BAR = 2,
    BAZ,
}

Int-backed enum:

@Backing(type="int")
enum IntEnum {
    FOO = 1000,
    BAR = 2000,
    BAZ,
}

Long-backed enum:

@Backing(type="long")
enum LongEnum {
    FOO = 100000000000,
    BAR = 200000000000,
    BAZ,
}

When a value is omitted (as with BAZ above), it is automatically assigned the previous value plus one. So BAZ would be 3, 2001, and 200000000001 respectively.

Backing Type Mapping

The AIDL backing type determines the Rust integer type used inside the generated newtype struct:

AIDL Backing TypeRust Type
bytei8
inti32
longi64

Using Enums in Rust

Enum values are accessed as associated constants on the generated struct. The generated type derives Debug, Default, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, and Hash, plus serialization traits automatically.

#![allow(unused)]
fn main() {
// Access enum values as associated constants
let e = ByteEnum::FOO;
let result = service.RepeatByteEnum(e)?;
assert_eq!(result, ByteEnum::FOO);
}

Enums work naturally with arrays and vectors:

#![allow(unused)]
fn main() {
// Enums can be used in arrays
let input = [ByteEnum::FOO, ByteEnum::BAR, ByteEnum::BAZ];
let mut repeated = vec![];
let reversed = service.ReverseByteEnum(&input, &mut repeated)?;
}

Each generated enum type provides an enum_values() method that returns a fixed-size array ([Self; N]) of all defined values, which is useful for iteration and validation:

#![allow(unused)]
fn main() {
// enum_values() returns all defined values
let all_values = ByteEnum::enum_values();
}

Enums in Service Interfaces

Enums are commonly used as parameters and return types in AIDL interfaces:

interface ITestService {
    ByteEnum RepeatByteEnum(ByteEnum token);
    IntEnum RepeatIntEnum(IntEnum token);
    LongEnum RepeatLongEnum(LongEnum token);

    ByteEnum[] ReverseByteEnum(in ByteEnum[] input, out ByteEnum[] repeated);
}

The generated Rust trait methods use the enum types directly, providing compile-time type safety across the IPC boundary.

Union Types

AIDL unions represent a tagged value that holds exactly one of several possible fields at a time. They map to Rust enum types, which are a natural fit since Rust enums are sum types with variants.

Defining Unions in AIDL

Here is a union definition from the rsbinder test suite:

@RustDerive(Clone=true, PartialEq=true)
union Union {
    int[] ns = {};
    int n;
    int m;
    @utf8InCpp String s;
    @nullable IBinder ibinder;
    @utf8InCpp List<String> ss;
    ByteEnum be;

    const @utf8InCpp String S1 = "a string constant in union";
}

Key points about union definitions:

  • The first field is the default. When a union is default-constructed, it takes the value of the first field. In this example, the default is ns initialized to an empty array {}. A first field without an initializer takes its type's Rust Default, as in AOSP's Rust backend; for an enum that is the backing value 0, not the first enumerator.
  • Fields can have different types, including primitives, strings, arrays, other AIDL types, and even binder references.
  • Constants can be defined inside unions, independent of the union's variants.
  • @RustDerive is recommended so the generated Rust type supports Clone and PartialEq.

Using Unions in Rust

The AIDL union generates a Rust enum. Because AIDL types are organized into modules, the union type and its enum variants live inside a module named after the union. Variants are accessed as Union::Union::VariantName(...). A variant's name is its field's name with only the first letter uppercased, as in AOSP's Rust backend: ns becomes Ns, nullable_iface becomes Nullable_iface, and URL stays URL. Two fields whose names differ only in the first letter's case (foo and Foo) are therefore rejected, as AOSP rejects them.

#![allow(unused)]
fn main() {
// Default value is the first field
assert_eq!(Union::Union::default(), Union::Union::Ns(vec![]));

// Creating union variants
let u1 = Union::Union::N(42);
let u2 = Union::Union::S("hello".into());
let u3 = Union::Union::Be(ByteEnum::FOO);
}

Constants defined inside a union are accessed directly on the module, not through a variant:

#![allow(unused)]
fn main() {
// Constants defined in the union
let s = Union::S1;  // "a string constant in union"

// Using a constant as a union variant value
let u = Union::Union::S(Union::S1.to_string());
}

Union Tags

Each union has an associated Tag type that identifies which variant is currently active. (Like AIDL enums, it is generated as a newtype struct whose variants are exposed as associated constants such as Union::Tag::n. It is backed by i32, or by i8 in a @FixedSize union, as in AOSP.) Tags are useful when you need to inspect or communicate which field a union holds without extracting the value itself.

#![allow(unused)]
fn main() {
let result = service.GetUnionTags(&[
    Union::Union::N(0),
    Union::Union::Ns(vec![]),
])?;
assert_eq!(result, vec![Union::Tag::n, Union::Tag::ns]);
}

Tags can also be used in match expressions when implementing service logic. Here is an example from the test service implementation:

#![allow(unused)]
fn main() {
fn GetUnionTags(
    &self,
    input: &[Union::Union],
) -> Result<Vec<Union::Tag>> {
    Ok(input.iter().map(|u| match u {
        Union::Union::Ns(_) => Union::Tag::ns,
        Union::Union::N(_) => Union::Tag::n,
        Union::Union::M(_) => Union::Tag::m,
        Union::Union::S(_) => Union::Tag::s,
        Union::Union::Ibinder(_) => Union::Tag::ibinder,
        Union::Union::Ss(_) => Union::Tag::ss,
        Union::Union::Be(_) => Union::Tag::be,
    }).collect())
}
}

Unions Containing Enums (EnumUnion)

Unions can contain enum types as fields and specify default values using enum constants:

@RustDerive(Clone=true, PartialEq=true)
union EnumUnion {
    IntEnum intEnum = IntEnum.FOO;
    LongEnum longEnum;
    /** @deprecated do not use this */
    int deprecatedField;
}

In this example, the default value is the first field (intEnum) initialized to IntEnum.FOO. In Rust:

#![allow(unused)]
fn main() {
assert_eq!(EnumUnion::default(), EnumUnion::IntEnum(IntEnum::FOO));
}

A /** @deprecated note */ block on a field becomes #[deprecated = "note"] on the generated variant (#[deprecated] when there is no note). The generated module allows the lint for its own plumbing, so only your code's uses warn — which fails a -D warnings build until you migrate or allow it.

Nested Unions

Unions can also be nested inside other unions:

union UnionInUnion {
    EnumUnion first;
    int second;
}

This allows building complex tagged-value hierarchies that are fully type-safe on the Rust side.

Tips

  • Specify @Backing explicitly. It is optional — the default is byte — but stating it makes the wire format unambiguous to a reader.
  • Handle values you never declared. An AIDL enum is an integer on the wire, so a peer built against a newer definition can send one the current enum does not name. That is the point of the newtype mapping; do not assume the set is closed.
  • Tags keep the AIDL field names, so they are lowercase: Union::Tag::ns, not Union::Tag::Ns.

Further Reading

  • Android AIDL documentation -- the upstream reference for AIDL syntax and semantics
  • AIDL annotation reference -- details on @Backing, @RustDerive, and other annotations
  • The rsbinder test suite at tests/aidl/ and tests/src/test_client.rs contains comprehensive examples of enum and union usage