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 NewStatus and
NewStatusOr<T> from @abseil-cpp//absl/status (layout-compatible with the
corresponding C++ types). For example:
absl::Status Foo();
absl::StatusOr<int> Bar();
This becomes:
#![allow(unused)]
fn main() {
use status::{NewStatus as Status, NewStatusOr as StatusOr};
pub fn Foo() -> Status { ... }
pub fn Bar() -> StatusOr<i32> { ... }
}
Calling C++ APIs using Status
To enable absl::Status and absl::StatusOr bindings for C++ libraries, enable
local_defines = ["CRUBIT_NEW_STATUS"] on the cc_library (TODO(b/490215742): clean
this up when the old API is removed; note that local_defines is preferred over
defines to prevent leaking the macro transitively to downstream dependencies):
cc_library(
name = "cpp_api",
srcs = ["cpp_api.cc"],
hdrs = ["cpp_api.h"],
aspect_hints = [
"//features:supported",
],
local_defines = ["CRUBIT_NEW_STATUS"],
deps = [
"@abseil-cpp//absl/status",
"@abseil-cpp//absl/status:statusor",
],
)
C++ functions returning Status/StatusOr can be defined as normal:
{{ #include ../../examples/types/absl_status/cpp_api.h }}
…and will return NewStatus / NewStatusOr<T> in Rust:
{{ #include ../../examples/types/absl_status/user_of_cpp_api.rs }}
Calling Rust APIs using Status
Rust APIs can directly return NewStatus or NewStatusOr<T> in public
functions:
{{ #include ../../examples/types/absl_status/rust_api.rs }}
cc_bindings_from_rust will automatically generate C++ bindings returning
absl::Status and absl::StatusOr<T>:
{{ #include ../../examples/types/absl_status/user_of_rust_api.cc }}
Do not use StatusWrapper or old Result-based Status / StatusOr
aliases.
Working with Status in Rust
Construction and Conversion
To construct status instances in Rust, use status::ok and status::err:
#![allow(unused)]
fn main() {
use status::{err, ok, NewStatus as Status, NewStatusOr as 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::{err as is_err, ok as is_ok, status_is};
}
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(StatusCode::Internal)));
}
Types inside StatusOr must be layout-compatible
NewStatusOr<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:
{{ #include ../../examples/types/absl_status/cpp_api.h }}
In Rust, create an instance of the message and pass its mutable view
(.as_mut()):
{{ #include ../../examples/types/absl_status/user_of_cpp_api.rs }}
Migration and Future Evolution
NewStatus and NewStatusOr<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. Once the codebase-wide
migration is complete, NewStatus and NewStatusOr will become the default
Status and StatusOr types.