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

absl::Status in Rust

In Google C++, the standard types for communicating an error are absl::Status and absl::StatusOr<T>. In Rust, these are represented using Status and StatusOr<T> from @abseil-cpp//absl/status, which are layout-compatible with the corresponding C++ types. For example:

absl::Status Foo();
absl::StatusOr<int> Bar();

This becomes:

#![allow(unused)]
fn main() {
use status::{Status, StatusOr};

pub fn Foo() -> Status { ... }
pub fn Bar() -> StatusOr<i32> { ... }
}

Calling C++ APIs using Status

Rust code that uses the status types depends on @abseil-cpp//absl/status:status_rust.

C++ functions returning Status/StatusOr return the corresponding Rust types:

absl::Status ReturnsStatus(bool ok);

absl::StatusOr<int> ReturnsStatusOrInt(bool ok);

absl::Status ReturnsStatusWithProto(
    let status = cpp_api::ReturnsStatus(true);

    let status = cpp_api::ReturnsStatus(false);

    let status_or_int = cpp_api::ReturnsStatusOrInt(true);

    let status_or_int = cpp_api::ReturnsStatusOrInt(false);

    let status = cpp_api::ReturnsStatusWithProto(true, msg.as_mut());

    let status = cpp_api::ReturnsStatusWithProto(false, msg.as_mut());

Calling Rust APIs using Status

Rust APIs can directly return Status or StatusOr<T> in public functions:

use status::{err, ok, Status, StatusOr};

pub fn returns_status(is_ok: bool) -> Status {
    if is_ok {
        ok(())
    } else {
        err(status::internal("Something went wrong, oh no!"))
    }
}

pub fn returns_status_or_int(is_ok: bool) -> StatusOr<i32> {
    if is_ok {
        ok(42)
    } else {
        err(status::internal("Something went wrong, oh no!"))
    }
}

cpp_api_from_rust will automatically generate C++ bindings returning absl::Status and absl::StatusOr<T>:

  EXPECT_OK(rust_api::returns_status(true));
  EXPECT_FALSE(rust_api::returns_status(false).ok());

  auto status_or_int = rust_api::returns_status_or_int(true);

  EXPECT_FALSE(rust_api::returns_status_or_int(false).ok());

Working with Status in Rust

Construction and Conversion

To construct status instances in Rust, use status::{ok, err}:

#![allow(unused)]
fn main() {
use status::{err, ok, Status, StatusOr};

let success: Status = ok(());
let value: StatusOr<i32> = ok(42);
let failure: Status = err(status::internal("error message"));
}

Existing Rust Result types can be converted using status::into_new_status(...).

Testing with Googletest

When using googletest alongside status::{ok, err}, import matchers with aliases to avoid name collisions:

#![allow(unused)]
fn main() {
use googletest::matchers::{eq, err as is_err, ok as is_ok, status_is};
use status::StatusCodeError;
}

You can then assert on Status or StatusOr values:

#![allow(unused)]
fn main() {
expect_that!(result, is_ok(eq(&42)));
expect_that!(result, is_err(status_is(StatusCodeError::INTERNAL)));
}

Types inside StatusOr must be layout-compatible

StatusOr<T> is layout-compatible with C++ absl::StatusOr<T>, enabling zero-cost passing across the FFI boundary. However, this layout compatibility requires that the inner payload type T is also layout-compatible between C++ and Rust.

Types that are bridged across the FFI boundary via runtime conversions (such as Protocol Buffers, which are not layout-compatible between C++ and Rust; see b/534900713, or std::string) do not share the same in-memory representation between C++ and Rust. Because T is stored directly inside StatusOr<T>, absl::StatusOr<T> cannot be used with these types.

As a result, functions that accept or return absl::StatusOr<MyProto> (or c9::Co<absl::StatusOr<MyProto>>) cannot be bound directly by Crubit.

Workaround: Out-Parameters with Proto Views

Instead of returning absl::StatusOr<MyProto> directly by value across FFI, the C++ API can return absl::Status and accept a mutable reference (MyProto&) output parameter:

absl::Status ReturnsStatusWithProto(
    bool ok, crubit::absl_status_example::ExampleProto& out);

In Rust, create an instance of the message and pass its mutable view (.as_mut()):

#[gtest]
fn test_returns_status_with_proto() {
    let mut msg = example_rust_proto::ExampleProto::new();
    let status = cpp_api::ReturnsStatusWithProto(true, msg.as_mut());
    expect_that!(status, is_ok(eq(&())));
    expect_that!(msg.value(), eq(42));

    let mut msg = example_rust_proto::ExampleProto::new();
    let status = cpp_api::ReturnsStatusWithProto(false, msg.as_mut());
    expect_that!(
        status,
        status_is(StatusCodeError::INTERNAL).with_message(eq("Something went wrong, oh no!"))
    );
}

Migration and Future Evolution

Status and StatusOr<T> are layout-compatible with absl::Status and absl::StatusOr<T>, enabling zero-cost passing across the FFI boundary as well as use in struct fields, arrays, or behind pointers and references. Error handling with ? is supported via the Try trait.

Deprecated API

Status and StatusOr were previously named NewStatus and NewStatusOr, which remain as aliases; prefer Status and StatusOr in new code. The legacy Result-based OldStatus and OldStatusOr are deprecated, as is StatusWrapper; don’t use them in new code. CRUBIT_NEW_STATUS is no longer needed and is ignored. Targets that have not migrated yet opt into the old bindings with local_defines = ["CRUBIT_OLD_STATUS"].