Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

Language mapping from IDL to Rust.

Naming Conventions

Rust uses the following naming conventions:

ItemConvention
Cratessnake_case or kebab-case
Modulessnake_case
TypesPascalCase
TraitsPascalCase
Enum variantsPascalCase
Functionssnake_case
Methodssnake_case
General constructorsnew or with_more_details
Conversion constructorsfrom_some_other_type
Macrossnake_case!
Local variablessnake_case
StaticsSCREAMING_SNAKE_CASE
ConstantsSCREAMING_SNAKE_CASE
Type parametersconcise PascalCase, usually single uppercase letter: T
Lifetimesshort lowercase, usually a single letter: 'a, 'de, 'src

Most of the above naming conventions are enforced by the compiler. Deviating from the above scheme will result in a compilation warning. For that reason, the generated code should follow the above naming scheme to the extent possible.

IDL to Rust Name Transformation

IDL names are automatically transformed to match Rust conventions. The following transformations are applied:

IDL ItemTransformationExample
Modulessnake_caseMyModulemy_module
StructsPascalCasemy_structMyStruct
UnionsPascalCasemy_unionMyUnion
EnumsPascalCasemy_enumMyEnum
EnumeratorsPascalCaseMY_VALUEMyValue
BitmasksPascalCasemy_bitmaskMyBitmask
Bit flagssnake_caseMY_FLAGmy_flag
TypedefsPascalCasemy_aliasMyAlias
InterfacesPascalCasemy_interfaceMyInterface
ExceptionsPascalCasemy_exceptionMyException
ConstantsSCREAMING_SNAKE_CASEmyConstMY_CONST
Struct memberssnake_caseMyFieldmy_field
Union variantsPascalCasemy_variantMyVariant
Operationssnake_caseDoSomethingdo_something
Parameterssnake_caseMyParammy_param

Suffix Stripping

Common type suffixes are stripped before case conversion to produce cleaner names:

  • _t suffix: my_type_tMyType
  • _e suffix: my_enum_eMyEnum

Enum Prefix Stripping

Enumerator names that share a common prefix with their enum type have that prefix stripped to avoid redundancy:

// IDL
enum Color {
    COLOR_RED,
    COLOR_GREEN,
    COLOR_BLUE
};
#![allow(unused)]
fn main() {
// Rust - prefix "COLOR_" is stripped
pub enum Color {
    Red,
    Green,
    Blue,
}
}

Protected names

Rust groups keywords into two categories: weak and strict.

Strict keywords

Strict keywords are keywords which can only be used in their correct contexts, which is to say they cannot be used as the names of items, variables, fields and variants, type parameters, macros or crates.

Generated files, modules, types and variables may not use strict keywords.

Keywords
asbreakconstcontinuecrateelse
enumexternfalsefnforif
implinletloopmatchmod
movemutpubrefreturnself
Selfstaticstructsupertraittrue
typeunsafeusewherewhileasync
awaitdynabstractbecomeboxdo
finalmacrooverrideprivtypeofunsized
virtualyieldtry

Strict keywords will be escaped in the generated Rust code by appending an underscore to the name, e.g. module_.

Weak keywords

Weak keywords have special meaning only in certain contexts. None of the current weak keywords affect the naming of types or variables. Thus, weak keywords are not reserved and may be freely used.

KeywordContext
unionOnly a keyword when used in a union declaration.
'staticCannot be used as a generic lifetime parameter or loop label.

For example, the following is perfectly valid Rust code:

#![allow(unused)]
fn main() {
fn union() {
    union union<'union> {
        union: &'union union<'union>
    }
}
}

Anonymous Types

Anonymous union and struct types are not supported. Rust mandates that all types are named, i.e. it does not have a concept of anonymous types beyond tuples. Other interim anonymous types do not need any special handling, as they can be expressed directly in the type system.

Comments

The Rust compiler comes bundled with rustdoc, a tool for generating documentation from comments in the code.

  • Lines starting with // are implementation comments and are omitted from the generated documentation.
  • Lines starting with /// are considered documentation comments.
  • Lines starting with //! are module-level or crate-level documentation. Such comments may only appear at the beginning of a file or above a module. Using it other places is treated as an error.

Documentation comments attached to IDL definitions are emitted using /// style comments. Module-level comments are currently not emitted in the generated code.

IDL comments that are denoted by // should be omitted from the generated code for the sake of brevity.

Mapping for Modules

Files

Files implicitly become modules in Rust. Since this is not the case for IDL, files and how they are organized does not affect the generated Rust code.

Includes

Files included by the processed IDL file will be publicly imported in the generated Rust module.

// IDL
#include "LocalInclude.idl"
#include <SystemInclude.idl>
#![allow(unused)]
fn main() {
// Rust
pub mod local_include;
pub mod system_include;
}

Note the different naming scheme: the file names were converted to snake_case.

Modules

To correctly map the scope of each IDL type, IDL modules are mapped to a hierarchy of Rust files. Given a set of IDL files, the compiler should generate the following:

  • A lib.rs file suitable for being included directly from Rust crates. It should publicly include all other modules, and it should contain all global top-level definitions.
  • For each IDL module, the compiler should generate a single file with the same name as the module. Type definitions that appear directly under this module should be placed in the same file.
  • If a module contains nested modules, the compiler should create a directory with the parent module’s name, and put the child module inside said directory. This process should repeat for modules nested inside the child module.
// IDL
struct MyGlobalStruct {};

module my_mod {
    struct MyModStruct {};

    module my_nested_module {
        module foo {
            struct FooStruct {};
        };
    };
};

The above modules will produce the following file hierarchy:

.
├── lib.rs
├── my_mod
│   ├── my_nested_module
│   │   └── foo.rs
│   └── my_nested_module.rs
└── my_mod.rs

MyGlobalStruct will be placed inside lib.rs; MyModStruct will be placed inside my_mod.rs, and all modules nested inside my_mod will be put in the my_mod directory.

Mapping for Primitives

The basic data types have the mappings shown below.

IDLRustDefault value
booleanboolfalse
octetu80
int8i80
uint8u80
int16i160
uint16u160
int32i320
uint32u320
int64i640
uint64u640
shorti160
longi320
floatf320_f32
doublef640_f64
long doublef640_f64
charchar'\x00'
wcharchar'\x00'
char8char'\x00'
char16char'\x00'

All of the above types are primitive, built-in types. Rust chars are guaranteed to be valid UTF-8 characters.

Limitations

Long Double

The IDL long double type is mapped to f64 in Rust, which is the same as double. Rust does not have not yet have native support for 80-bit or 128-bit floating point types in the standard library. This means that long double values may lose precision when used in Rust code. If higher precision is required, an implementation may provide a custom implementation of an f128-equivalent type.

There is an open RFC for adding support for an f128 type, but it is not yet stabilized.

Constants

IDL constants are mapped directly to Rust constants. Rust supports octal, hexadecimal and decimal integer literals. String constants will be mapped to string slices instead of String.

// IDL
const int32 MY_DECIMAL = 123;
const int32 MY_HEX = 0xFFF;
const int32 MY_OCTAL = 0655;
const string MY_STRING = "my string";
const octet MY_ARRAY[4] = {0, 1, 2, 3};
#![allow(unused)]
fn main() {
// Rust
pub const MY_DECIMAL: i32 = 123;
pub const MY_HEX: i32 = 0xFFF;
pub const MY_OCTAL: i32 = 0o655;
pub const MY_STRING: &str = "my_string";
pub const MY_ARRAY: [u8; 4] = [0, 1, 2, 3];
}

Complex types

Constants can only be used for types that can be constructed at compile-time, i.e. types whose constructor is const. For non-const types, statics must be used instead.

Rust does not permit code execution before main, i.e. dynamic initialization of statics must happen afterwards. Constants that require dynamic initialization – i.e. vectors and complex IDL types – must thus be explicitly initialized the first time the member is accessed. To avoid data races, the variable can be initialized using std::sync::LazyLock, a synchronization primitive which lazily initializes the value on first access. This is only done for types which contain non-const-constructible members.

// IDL
struct MyTrivialStruct {
    int32 x;
    int32 y;
    int32 z;
};

struct MyComplexStruct {
    string my_str;
};

const MyTrivialStruct MY_TRIVIAL_CONST = { ... };
const MyComplexStruct MY_COMPLEX_CONST = { ... };
#![allow(unused)]
fn main() {
// Rust
pub const MY_TRIVIAL_CONST: MyTrivialStruct = MyTrivialStruct { ... };

pub static MY_COMPLEX_CONST: ::std::sync::LazyLock<MyComplexStruct> =
    ::std::sync::LazyLock::new(|| MyComplexStruct { ... });
}

Typedefs

Typedefs of types correspond to type aliases in Rust. Type aliases may only be used for types and not traits. If a typedef defines an alias of an interface, it will instead be publicly re-exported under the specified name.

// IDL
typedef long T;
typedef sequence<long> S1;
typedef S1 S2;

interface A1;
typedef A1 A2;
#![allow(unused)]
fn main() {
pub type T = i32;
pub type S1 = Vec<i32>;
pub type S2 = S1;

// ...definitions for A1...
pub use A1 as A2;
}

Mapping for Enums

Enums are mapped to trivial enums in Rust. Each generated enum has a const constructor, and should implement the Default trait.

For convenience, each enum also implements std::str::FromStr and std::fmt::Display, for respectively converting the enum from and to its string representation. The Display implementation automatically provides ToString.

Enums are always trivial (they consist only of discriminant values) and always have total order, so they derive Copy, Eq, Ord, and Hash in addition to the base traits. See Derived Traits for details on trait derivation rules.

Representation

The #[repr] attribute specifies the underlying integer type used to store the enum discriminant. This is determined by the @bit_bound annotation in IDL:

@bit_boundRust #[repr]
8u8
16u16
32 (default)u32
64u64

If @bit_bound is not specified, the default is 32 bits (u32).

Example

@bit_bound(32)
enum MyEnum {
    ONE,
    TWO,
    @value(9) NINE
};
#![allow(unused)]
fn main() {
#[repr(u32)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum MyEnum {
    One,
    Two,
    Nine = 9,
}

impl MyEnum {
    #[must_use]
    pub const fn new() -> Self {
        MyEnum::One
    }
}

impl FromStr for MyEnum {
    // ...
}

impl Display for MyEnum {
    // ...
}

impl Default for MyEnum {
    fn default() -> Self {
        Self::new()
    }
}
}

Mapping for String Types

Both bounded and unbounded IDL string types are mapped to String from the standard library.

// IDL
typedef string MyString;
typedef wstring MyWideString;
#![allow(unused)]
fn main() {
// Rust
pub type MyString = String;
pub type MyWideString = String;
}

Default Value

The default value for a string is an empty string:

#![allow(unused)]
fn main() {
String::new()
}

Bounded Strings

IDL allows specifying a maximum length for strings. This bound is not enforced at the type level in Rust, but is checked during serialization. An implementation may choose to provide a custom BoundedString<N> type for such purposes.

// IDL
typedef string<100> BoundedString;
#![allow(unused)]
fn main() {
// Rust - bound is not reflected in the type
pub type BoundedString = String;
}

Wide String Types

The char type in Rust is always a UTF-8 character. The String type is only a collection of chars; for that reason, both IDL strings and wide strings are mapped to the String type in Rust.

Mapping for Sequence Types

Bounded and unbounded IDL sequence types are mapped to Vec in Rust.

// IDL
typedef sequence<T> V1;
typedef sequence<T, 2> V2;
typedef sequence<V1> V3;
#![allow(unused)]
fn main() {
// Rust
type V1 = Vec<T>;
type V2 = Vec<T>;
type V3 = Vec<V1>;
}

Default Value

The default value for a sequence is an empty vector:

#![allow(unused)]
fn main() {
Vec::new()
}

Bounded Sequences

IDL allows specifying a maximum size for sequences. This bound is not enforced at the type level in Rust, but is checked during serialization. An implementation may choose to provide a custom BoundedVec<T, N> type for such purposes.

// IDL
typedef sequence<int32, 100> BoundedSeq;
#![allow(unused)]
fn main() {
// Rust - bound is not reflected in the type
pub type BoundedSeq = Vec<i32>;
}

Mapping for Array Types

IDL arrays are mapped to Rust arrays, which allows the definition of statically initialized data.

// IDL
typedef float F[10];
typedef string V[10];
typedef string M[1][2][3];
#![allow(unused)]
fn main() {
// Rust
pub type F = [f32; 10];
pub type V = [String; 10];
pub type M = [[[String; 3]; 2]; 1];
}

Default Value

The default value for an array is an array where each element is initialized to its type’s default value. For example, an array of 10 integers defaults to [0; 10], and an array of strings defaults to an array of empty strings.

Mapping for Map Types

IDL map types are mapped to BTreeMap from the Rust standard library. A BTreeMap is used rather than HashMap because it provides deterministic ordering, which is important for serialization consistency.

// IDL
typedef map<string, int32> StringToInt;
typedef map<int32, sequence<string>> IntToStrings;
#![allow(unused)]
fn main() {
// Rust
pub type StringToInt = ::std::collections::BTreeMap<String, i32>;
pub type IntToStrings = ::std::collections::BTreeMap<i32, Vec<String>>;
}

Default Value

The default value for a map is an empty map:

#![allow(unused)]
fn main() {
::std::collections::BTreeMap::new()
}

Bounded Maps

IDL allows specifying a maximum size for maps. This bound is not enforced at the type level in Rust, but is checked during serialization. An implementation may choose to provide a custom BoundedMap<K, V, N> type for such purposes.

// IDL
typedef map<string, int32, 100> BoundedMap;
#![allow(unused)]
fn main() {
// Rust - bound is not reflected in the type
pub type BoundedMap = ::std::collections::BTreeMap<String, i32>;
}

Key Type Requirements

Map keys must be types that implement Ord in Rust (required by BTreeMap). This means the key type must have total order. Floating-point types (f32, f64) cannot be used as map keys because they do not implement Ord.

Constant Maps

Map constants are initialized using the BTreeMap::from constructor with an array of key-value tuples:

// IDL
const map<string, int32> MY_MAP = {
    {"one", 1},
    {"two", 2},
    {"three", 3}
};
#![allow(unused)]
fn main() {
// Rust
pub static MY_MAP: ::std::sync::LazyLock<::std::collections::BTreeMap<String, i32>> =
    ::std::sync::LazyLock::new(|| {
        ::std::collections::BTreeMap::from([
            ("one".into(), 1),
            ("two".into(), 2),
            ("three".into(), 3),
        ])
    });
}

Note that map constants containing non-trivial types (like String keys) use LazyLock for lazy initialization. See Constants for details.

Mapping for Structured Types

Generated structs have public members. This design choice is intentional for data-carrying types:

  • Simplicity: Avoids the need for getter/setter methods in three variants (by-value, by-reference, by-mutable-reference) for each field.
  • Ergonomics: Enables direct field access and struct literal syntax.
  • Consistency: Matches Rust conventions for plain data structures.
  • Flexibility: Allows partial initialization with ..Default::default().
  • Partial moves: Enables moving individual fields out of a struct without consuming the entire value.
#![allow(unused)]
fn main() {
// Rust
let _ = MyStruct {
    my_int: 5,
    my_str: "my string".into(),
    my_vec: vec![1, 2, 3],
};
}

In addition, this plays nicely alongside the Default trait: users can specify some values, whilst defaulting the rest. For example:

#![allow(unused)]
fn main() {
let _ = MyStruct {
    my_int: 5,
    ..Default::default()
};
}

The variables that were not named in such a construct will be filled in with the values acquired from the default function, i.e. the default values specified in IDL.

Derived Traits

All generated types (struct, enum, union, bitmask, exception) automatically derive a set of Rust traits. Some traits are always derived, while others are conditionally derived based on recursive type analysis.

Always Derived

The following traits are always derived for all generated types:

TraitPurpose
CloneEnables deep copying of values
DebugEnables formatting with {:?}
PartialEqEnables equality comparison (==, !=)
PartialOrdEnables ordering comparison (<, >, etc)

Conditionally Derived

The compiler performs a recursive analysis of each type to determine whether additional traits can be derived. This analysis examines all members, parent types, and nested types to make the determination.

Triviality Analysis (Copy)

A type is considered trivial if it consists only of:

  • Primitive types (bool, integers, char)
  • Arrays of trivial types
  • Other trivial user-defined types

A type is not trivial if it contains:

  • String or bounded/unbounded string types
  • Vec (sequences)
  • BTreeMap (maps)
  • Recursive/circular types
  • Any, Fixed, or interface types

Trivial types derive Copy, which enables bitwise copying without explicit .clone() calls.

Total Order Analysis (Eq, Ord, Hash)

A type has total order if all its members can form a well-ordered set. This requires that all member types satisfy:

  • The type is not a floating-point type (f32, f64)
  • All nested/member types also have total order

Floating-point types break total order because NaN != NaN, which violates the reflexivity property required by Eq.

Types with total order derive:

TraitPurpose
EqGuarantees reflexive equality (x == x)
OrdEnables total ordering comparisons
HashEnables use as keys in hash-based collections

Default Trait

All generated types implement the Default trait. The implementation delegates to the new() constructor, which initializes all members to their default values (either from @default annotations or type-specific defaults).

#![allow(unused)]
fn main() {
impl Default for MyStruct {
    fn default() -> Self {
        Self::new()
    }
}
}

Summary Table

TraitCondition
CopyType is trivial (no heap allocation, not recursive)
CloneAlways
DebugAlways
DefaultAlways (via new() constructor)
EqType has total order (no floating-point members)
PartialEqAlways
OrdType has total order (no floating-point members)
PartialOrdAlways
HashType has total order (no floating-point members)

Example

// IDL - trivial type with total order
struct Point {
    int32 x;
    int32 y;
};

// IDL - non-trivial type with total order
struct Person {
    string name;
    int32 age;
};

// IDL - non-trivial type without total order
struct Measurement {
    float value;
    string unit;
};
#![allow(unused)]
fn main() {
// Trivial + total order: derives Copy, Eq, Ord, Hash
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Point {
    pub x: i32,
    pub y: i32,
}

// Non-trivial + total order: derives Eq, Ord, Hash but not Copy
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Person {
    pub name: String,
    pub age: i32,
}

// Non-trivial + no total order: only base traits
#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub struct Measurement {
    pub value: f32,
    pub unit: String,
}
}

Example

// IDL
struct MyStruct {
    unsigned long my_int;
    string my_str;
    sequence<int32> my_vec;
};
#![allow(unused)]
fn main() {
#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub struct MyStruct {
    pub my_int: u32,
    pub my_str: String,
    pub my_vec: Vec<i32>,
}

impl MyStruct {
    pub fn new() -> Self {
        Self {
            my_int: 0,
            my_str: String::new(),
            my_vec: Vec::new(),
        }
    }
}

impl Default for MyStruct {
    fn default() -> Self {
        Self::new()
    }
}
}

Nested Types

Nested or scoped types will be placed inside a module with the name of the parent scope.

// IDL
struct MyA {
    struct MyB {
        struct MyC {};
    };
};
#![allow(unused)]
fn main() {
// Rust
struct MyA {};

pub mod my_a {
    struct MyB {};

    pub mod my_b {
        struct MyC {};
    };
};
}

Inheritance

Rust does not support inheritance. Following the IDL specification, fields from base types will be copied into the derived type.

// IDL
struct Parent {
    int64 parent_field;
};

struct Derived : Parent {
    int64 derived_field;
};
#![allow(unused)]
fn main() {
// Rust
pub struct Parent {
    pub parent_field: i64,
}

pub struct Derived {
    pub parent_field: i64,
    pub derived_field: i64,
}
}

Mapping for Union Types

Unions in IDL are mapped to Rust enums where each case label maps to an enum variant. Enums are easier to use and are better suited for pattern matching. The value of the discriminator is inferred during serialization by looking at the populated field.

In the event that a union has multiple switch cases mapped to a single value, the name of the case label is appended to the end of the member value. If not all cases are covered by the union, an implicit default case will be inserted.

Each union has a disc function which returns the corresponding enum value of the would-be discriminator, deduced from the populated variant. Similarly, each union implements the From<T> trait, where T is the type of the discriminant.

The derived traits for unions depend on their variant types. Unions containing heap-allocated types (like String) will not derive Copy. Unions containing floating-point types will not derive Eq, Ord, or Hash. See Derived Traits for the complete derivation rules.

// IDL
enum MyEnum {
    ONE,
    TWO,
    THREE,
    FOUR
};

union MyUnion switch(MyEnum) {
case ONE:
    string my_string;
case TWO:
case THREE:
    int32 my_int;
default:
    string default_value;
};
#![allow(unused)]
fn main() {
// Rust
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum MyEnum {
    One,
    Two,
    Three,
    Four,
}

#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub enum MyUnion {
    MyString(String),
    MyIntTwo(i32),
    MyIntThree(i32),
    DefaultValue(String),
}

impl MyUnion {
    pub fn new() -> Self {
        Self::MyString(String::new())
    }

    pub const fn disc(&self) -> MyEnum {
        match self {
            Self::MyString(_) => MyEnum::One,
            Self::MyIntTwo(_) => MyEnum::Two,
            Self::MyIntThree(_) => MyEnum::Three,
            Self::DefaultValue(_) => MyEnum::Four,
        }
    }
}

impl From<MyEnum> for MyUnion {
    fn from(disc: MyEnum) -> Self {
        // maps the discriminant to the correct variant
    }
}

impl Default for MyUnion {
    fn default() -> Self {
        Self::new()
    }
}
}

Bitmask Types

Bitmasks are mapped to newtype structs in Rust, where each value is an associated constant of the newtype. This effectively scopes the value of each constant into the bitmask type, and prevents doing bitwise operations with different types. The underlying integer can be accessed through the bits function.

The implementation uses a bitmask! macro to generate the type and all required trait implementations. The generated API is equivalent to the expanded form shown below.

Bitmasks are always trivial (they wrap a primitive integer) and have total order, so they derive Copy, Debug, Eq, Ord, and Hash. See Derived Traits for the general trait derivation rules.

The #[repr(transparent)] attribute guarantees that the newtype has the same memory layout as the underlying integer type. This ensures ABI compatibility when passing bitmasks across FFI boundaries and allows safe transmutation between the bitmask and its underlying type.

// IDL
@bit_bound(32)
bitmask MyBitmask {
    A,
    B,
    @position(5) C
};
#![allow(unused)]
fn main() {
// Rust
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[repr(transparent)]
pub struct MyBitmask(u32);

impl MyBitmask {
    pub const A: Self = Self(1 << 0);
    pub const B: Self = Self(1 << 1);
    pub const C: Self = Self(1 << 5);

    #[inline]
    pub const fn nil() -> Self {
        Self(0)
    }

    #[inline]
    pub const fn all() -> Self {
        Self(Self::A.0 | Self::B.0 | Self::C.0)
    }

    #[inline]
    pub const fn bits(&self) -> u32 {
        self.0
    }

    #[inline]
    pub const fn is_empty(&self) -> bool {
        self.0 == 0
    }

    #[inline]
    pub const fn contains(&self, rhs: Self) -> bool {
        (self.0 & rhs.0) == rhs.0
    }

    #[inline]
    pub fn clear(&mut self) {
        self.0 = 0
    }
}

impl ::std::ops::BitOr for MyBitmask {
    type Output = Self;

    #[inline]
    fn bitor(self, rhs: Self) -> Self::Output {
        Self(self.0 | rhs.0)
    }
}

impl ::std::ops::BitOrAssign for MyBitmask {
    #[inline]
    fn bitor_assign(&mut self, rhs: Self) {
        self.0 |= rhs.0;
    }
}

impl ::std::ops::BitXor for MyBitmask {
    type Output = Self;

    #[inline]
    fn bitxor(self, rhs: Self) -> Self::Output {
        Self(self.0 ^ rhs.0)
    }
}

impl ::std::ops::BitXorAssign for MyBitmask {
    #[inline]
    fn bitxor_assign(&mut self, rhs: Self) {
        self.0 ^= rhs.0
    }
}

impl ::std::ops::BitAnd for MyBitmask {
    type Output = Self;

    #[inline]
    fn bitand(self, rhs: Self) -> Self::Output {
        Self(self.0 & rhs.0)
    }
}

impl ::std::ops::BitAndAssign for MyBitmask {
    #[inline]
    fn bitand_assign(&mut self, rhs: Self) {
        self.0 &= rhs.0
    }
}

impl ::std::ops::Not for MyBitmask {
    type Output = Self;

    #[inline]
    fn not(self) -> Self::Output {
        Self(!self.0)
    }
}
}

Mapping for Exception Types

Rust does not support exceptions. It primarily relies on monads to convey results of operations.

Each exception defined in IDL will be mapped to a struct, as described before. In addition, each exception type should have a typedef for std::result::Result<T, E> where E is the exception type.

All exceptions implement the Error and Display traits. The derived traits follow the same rules as structs, based on member type analysis. See Derived Traits for details.

// IDL
exception MyException {
    string what;
};
#![allow(unused)]
fn main() {
#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub struct MyException {
    pub what: String,
}

impl MyException {
    pub fn new() -> Self {
        Self {
            what: String::new(),
        }
    }
}

impl Default for MyException {
    fn default() -> Self {
        Self::new()
    }
}

pub type MyExceptionResult<T> = Result<T, MyException>;

impl std::fmt::Display for MyException {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "MyException")
    }
}

impl std::error::Error for MyException {}
}

Mapping for Interfaces

Interfaces in IDL map to traits in Rust.

Parameter Passing

The parameter direction (in, inout, out) determines how parameters are passed in the generated Rust code.

in Parameters

For in parameters, the passing convention depends on the type:

Type CategoryRust Passing ConventionExample
Primitives (bool, integers, char, f32, f64)By valuevalue: i32
EnumsBy valuevalue: MyEnum
BitmasksBy valuevalue: MyBitmask
StringsImmutable slice referencevalue: &str
SequencesImmutable slice referencevalue: &[T]
Structs, Unions, ExceptionsImmutable referencevalue: &MyStruct
InterfacesBoxed trait objectvalue: Box<dyn MyInterface>

inout and out Parameters

Both inout and out parameters are passed as mutable references:

#![allow(unused)]
fn main() {
fn operation(&mut self, value: &mut i32);
}

Self Parameter

The self parameter depends on the function’s annotations:

AnnotationSelf Parameter
(none)&mut self
@const&self
@static(no self parameter)

Static functions require a where Self: Sized bound since they cannot be called through trait objects.

Exception Handling

Exceptions in IDL are mapped to Rust’s Result type:

Exception CountReturn Type
0T (or () for void)
1ExceptionNameResult<T> (type alias for Result<T, ExceptionName>)
2+Result<T, Box<dyn Error>>

Nested Types

Type definitions or aliases defined inside IDL interfaces will be defined in the outer scope, prefixed with the name of the interface.

Example

// IDL
interface MyInterface {
    boolean negate(boolean value);

    void increment(inout long value);

    @static
    float square_root(in float value);

    @const
    void print(Nested value) raises (MyException);

    int32 throws_multiple() raises(MyException, MyOtherException);

    struct Nested {};
};
#![allow(unused)]
fn main() {
// Rust
pub trait MyInterface {
    fn negate(&mut self, value: bool) -> bool;

    fn increment(&mut self, value: &mut i32);

    fn square_root(value: f32) -> f32
    where
        Self: Sized;

    fn print(&self, value: &Nested) -> MyExceptionResult<()>;

    fn throws_multiple(&mut self) -> Result<i32, Box<dyn Error>>;
}

pub struct MyInterfaceNested {}
}

Inheritance

Derived interfaces become additional trait bounds in Rust.

// IDL
interface CordA {};
interface CordB {};
interface CordC {};

interface Vector3 : CordA, CordB, CordC {};
#![allow(unused)]
fn main() {
// Rust
trait CordA {}
trait CordB {}
trait CordC {}

trait Vector3 : CordA + CordB + CordC {}
}

Note that the above means that any type that implements the Vector3 trait must manually implement CordA, CordB, and CordC.

Annotations

optional

The @optional annotation will cause member fields to be wrapped in an Option<T> type.

default and default_literal

The @default and @default_literal annotations affect the values generated in the new() function.

external

The @external annotation will cause the value to be wrapped in a Box<T>. This indicates that the value is allocated on the heap and owned through a pointer indirection.

const and static

Function prototypes may be annotated with @const or @static.

derive

The @derive annotation adds custom derive macros to generated types.

Rust offers access to the syntax tree during compilation through derive macros. In some cases, it may be desirable by the user to expand upon the generated types through such macros. To allow for this, the @derive annotation adds user-specified derive macros to the attached type.

For example:

@derive("MyDerive")
struct MyStruct {};

Results in the following Rust code:

#![allow(unused)]
fn main() {
#[derive(..., MyDerive)]
struct MyStruct {};
}

Rust does not require the relevant symbol to be explicitly imported. Thus, the user will have to be responsible for providing a qualified name to the macro.