Abseil contains two Status libraries within the absl/status
directory:
status
library containing an absl::Status
class for holding error
handling information, a set of canonical absl::StatusCode
error codes, and
associated utilities for generating and propagating status codes.statusor
library containing the absl::StatusOr<T>
class template for use
in returning either an absl::Status
error or an object of type T
. (This
StatusOr<T>
abstraction is similar to C++23’s
std::expected
.)absl::Status
Within Google, absl::Status
is the primary mechanism to gracefully handle
errors across API boundaries (and in particular across RPC boundaries). Some of
these errors may be recoverable, but others may not. Most functions which can
produce a recoverable error should be designed to return either an
absl::Status
or the similar absl::StatusOr<T>
, which holds either an object
of type T
or an error.
Example:
absl::Status MyFunction(absl::string_view filename, ...) {
...
// encounter error
if (error condition) {
return absl::InvalidArgumentError("bad mode");
}
// else, return OK
return absl::OkStatus();
}
Most operations in Abseil (or Google) code return an
absl::Status
(abbreviated Status
in the text below). A Status
is designed to either
return “OK” or one of a number of different error codes, corresponding to
typical error conditions. In almost all cases, when using absl::Status
you
should use the canonical error codes (of type absl::StatusCode
). These
canonical codes are understood across the codebase and will be accepted across
all API and RPC boundaries. A function which has a return value of Status
must be handled (and is marked ABSL_MUST_USE_RESULT
).
Status
for Returning ErrorsSuccess of any particular operation is indicated by a Status
error code of
“OK” (technically a status error code of absl::StatusCode::kOk
). API
developers should construct their operations to return absl::OkStatus()
upon
success, or an absl::StatusCode
upon another type of error (e.g. an
absl::StatusCode::kInvalidArgument
error). The API provides convenience
functions to construct each particular status code. (See
Canonical Errors below.)
For example, the following piece of code shows how to return an error encountered while implementing a file operation:
absl::Status Open(absl::string_view filename, absl::string_view mode, ...) {
if (...) return absl::OkStatus(); // Signal success
if (...) return absl::InvalidArgumentError("bad mode");
absl::Status result; // Default constructor creates an OK value as well.
if (...) {
// Short-hand for result = absl::Status(absl::StatusCode::kNotFound, ...)
result = absl::NotFoundError(absl::StrCat(filename, " is missing"));
} else {
...
}
return result; // could be "OK" or "NOT_FOUND"
}
A non-OK Status typically includes both an error code
(absl::StatusCode::kNotFound
, which maps to “NOT_FOUND”) and a message (“The
file.txt filename is missing”). The API provides code()
and message()
member
functions to retrieve these values. The error code is intended for programs to
examine (e.g., the caller might react differently based on the error code it
sees). The error message may be logged somewhere for a developer or SRE to
examine and find out what went wrong. The message is not intended for end users.
NOTE: low-level routines such as a file Open()
operation should typically not
log status values themselves, but should pass them up to the caller who will
have better context on how to handle any error.
Status
returns errors using an absl::StatusCode
, which is an enumerated type
indicating either no error (“OK”) or an error condition. These error codes map
to google.rpc.Code
RPC error codes.
E.g. an absl::StatusCode::kInvalidArgument
value corresponds to an RPC error
code of “INVALID_ARGUMENT”.
These canonical errors associated with absl::Status
are used throughout the
codebase. As a result, these error codes are somewhat generic. When constructing
an absl::Status
using one of these codes, you may want to provide more context
within the Status
object’s message.
For a full list of canonical error codes and advice on how to select the appropriate one for your use case, see the Choosing Canonical Error Codes guide.
Just as an API provider must properly construct and return absl::Status
, a
caller must properly handle receipt of that Status
. This involves checking
whether the operation completed successfully (checking for “OK”) and
determining the exact error and how to handle it, if the operation did not
succeed.
Instead of checking for a specific “OK” status code (e.g.
absl::StatusCode::kOk
), the Abseil Status library provides a Status::ok()
member function. Users handling status error codes should prefer checking for an
OK status using this Status::ok()
member function.
absl::Status
values can be logged directly without requiring any conversion to
a string value.
absl::Status my_status = DoSomething();
// Don't do this:
//
// if (my_status.code() == absl::StatusCode::kOk) { ... }
//
// Use the Status.ok() helper function:
if (!my_status.ok()) {
LOG(WARNING) << "Unexpected error " << my_status;
}
Similarly, instead of checking for specific absl::StatusCode
error codes such
as absl::StatusCode::kInvalidArgument
you may use helper functions such as
absl::IsInvalidArgument(status)
.
Handling multiple error codes may justify use of a switch
statement, but only
check for error codes you know how to handle; do not try to exhaustively match
against all canonical error codes. Errors that cannot be handled should be
logged and/or propagated for higher levels to deal with.
If you do use a switch
statement to discriminate status codes, make sure that
you also provide a default:
switch case, so that code does not break as other
canonical codes are added to the API.
absl::Status s = Open(filename, "r");
if (absl::IsNotFound(s)) {
s = Create(...);
}
if (!s.ok()) { // Either Open or Create failed
LOG(WARNING) << "Unexpected error " << s;
}
Suppose a function needs to return a value on success or, alternatively, a
Status
on error. The Abseil Status library provides an absl::StatusOr<T>
class template for this purpose. An absl::StatusOr<T>
represents a union of an
absl::Status
object and an object of type T
. The absl::StatusOr<T>
will
either contain an object of type T
(indicating a successful operation), or an
error (of type absl::Status
) explaining why such a value is not present. Note
that StatusOr<T>
cannot hold an OK status as that would imply a value should
be present.
In general, check the success of an operation returning an
absl::StatusOr<T>
like you would an absl::Status
by using the ok()
member function.
StatusOr<Foo> result = Calculation();
if (result.ok()) {
result->DoSomethingCool();
} else {
LOG(ERROR) << result.status();
}
Upon success, accessing the object held by an absl::StatusOr<T>
should be
performed via operator*
or operator->
, after a call to ok()
confirms that
the absl::StatusOr<T>
holds an object of type T
:
absl::StatusOr<int> i = GetCount();
if (i.ok()) {
updated_total += *i;
}
An absl::StatusOr<T*>
can be constructed from a null pointer like any other
pointer value, and the result will be that ok()
returns true
and value()
returns nullptr
. Checking the value of pointer in an absl::StatusOr<T>
generally requires a bit more care, to ensure both that a value is present and
that value is not null:
StatusOr<std::unique_ptr<Foo>> result = FooFactory::MakeNewFoo(arg);
if (!result.ok()) {
LOG(ERROR) << result.status();
} else if (*result == nullptr) {
LOG(ERROR) << "Unexpected null pointer";
} else {
(*result)->DoSomethingCool();
}
Our compilers produce errors if a Status
value returned by a function is
ignored. In some cases, ignoring the result is the correct thing to do, which
you can achieve by using IgnoreError()
:
// Don't let caching errors fail the response.
StoreInCache(request, response).IgnoreError();
Think carefully before using IgnoreError()
. Unless you have a good reason,
prefer to actually handle the return value: perhaps you can verify that the
result matches the error you are expecting, or perhaps you can export it for
monitoring.
Use Status::Update()
to keep track of the first non-ok status encountered in a
sequence. Update()
will overwrite an existing “OK” status, but will not
overwrite an existing error code of another value.
For example, suppose you want to execute two operations (regardless of whether or not the first operation failed), but want to return an error if either of the operations failed. Instead of:
absl::Status s = Operation1();
absl::Status s2 = Operation2();
if (s.ok()) s = s2;
use
absl::Status s = Operation1();
s.Update(Operation2());
Update()
will preserve the information of the first encountered error, such as
its error code, message, and any payloads.