Types

Overview

/overview/status#types outlines the current and future status of Crubit's type support.

In brief, Crubit supports:

  • Primitive types (/types/primitive), such as float or i32.
  • Pointer types (/types/pointer), such as float* or *const i32, including function pointers.
  • User-defined types, with some language-specific rules and restrictions. (See /cpp and /rust).

ABI-Compatibility

Certain references to C++ or Rust types will not receive Crubit bindings. Some types may only be usable in certain locations due to current Crubit limitations, inherent properties of the type, or both. Supported types fall into one of three categories ranging from "most widely supported" to "most restricted":

  • ABI-compatible: these types have a C-ABI-equivalent representation which can be used anywhere a value of this type is expected from both C++ and Rust.
  • Layout-compatible: these types have equivalent in-memory representations in C++ and Rust but cannot be represented using standard C ABI. These types will only be usable as by-value function arguments if they are C++-movable. For example, Box<i32> is not C++-movable because it has no nullptr / moved-from representation.
  • Bridged: these types may have different in-memory representations in C++ and Rust, and so can only be passed by-value between the two languages. Examples include Rust tuples, which are bridged by-value into C++ std::tuple.
Level of SupportExamplePass by-referencePass by-valueReturn by-valueFieldsIn Function Pointer Types
ABI Compatiblei32YYYYY
Layout-compatible C++ typeabsl::string_viewYif Rust-movable1if Rust-movable2YN
Layout-compatible Rust typeUserDefinedStructYif C++ movable3YYN
Bridged(i32, i32)NYYNN
1

See /cpp/classes_and_structs#trivially_relocatable

2

See /cpp/classes_and_structs#trivially_relocatable

3

See /rust/movable_types

NOTE: All primitive and pointer types are ABI-compatible. However, due to b/369895805, all non-bridged user-defined types are only layout-compatible.

In the following examples, foo receives bindings, but bad_foo will not receive bindings, because while the types it uses in its function signature are supported by Crubit, they are not supported in this particular context.

C++

void foo(int32_t);
void foo(void (*)(int32_t));
void foo(Status);
struct LayoutCompatibleType {
    UnsupportedType field;
    // or [[no_unique_addres]] int field; or...
};

// foo cannot receive bindings, because the function pointer type
// does not work with non-ABI-compatible types
void bad_foo(void (*)(LayoutCompatibleType));
// foo cannot receive bindings, because bridged types cannot be passed
// by reference
void bad_foo(const Status&);

Rust

#![allow(unused)]
fn main() {
pub fn foo(_: i32) {}
pub fn foo(_: fn(i32)) {}
pub fn foo(_: Status) {}
}
#![allow(unused)]
fn main() {
struct LayoutCompatibleType {
    field: UnsupportedType
}

// foo cannot receive bindings, because the function pointer type
// does not work with non-ABI-compatible types
pub fn bad_foo(_: fn(LayoutCompatibleType)) {}
}
#![allow(unused)]
fn main() {
// foo cannot receive bindings, because bridged types cannot be passed
// by reference
fn bad_foo(_: &Status) {}
}

Bidirectionality

Usually, the mapping of types between languages is bidirectional. For example, a C++ function which returns an int32_t will become a Rust function returning an i32, and vice versa. In some sense, an i32 is an int32_t.

However, in other cases, the mapping is not reversible. C++ and Rust have types or aliases that the other language does not. For example, isize becomes intptr_t, but intptr_t is (on some platforms) the same type as int64_t, and so intptr_t becomes i64.