string_view
operator+
vs. StrCat()
absl::Status
std::bind
absl::optional
and std::unique_ptr
absl::StrFormat()
make_unique
and private
Constructors.bool
explicit
= delete
)switch
Statements Responsibly= delete
AbslHashValue
and Youcontains()
std::optional
parametersif
and switch
statements with initializersinline
Variablesstd::unique_ptr
Must Be MovedAbslStringify()
vector.at()
auto
for Variable DeclarationsOriginally posted as TotW #181 on July 9, 2020
Updated 2020-09-02
Quicklink: abseil.io/tips/181
StatusOr<Readability>
: you don’t have to choose!
When the time comes to access the value inside an absl::StatusOr<T>
object, we
should strive to make that access safe, clear, and efficient.
Note: This tip attempts to highlight well lit paths providing guidance for typical use-cases. It is not intended to be exhaustive. If you encounter edge cases, consider the recommendations and reasoning here and exercise judgement.
Accessing the value held by a StatusOr
should be performed via operator*
or
operator->
, after a call to ok()
has verified that the value is present.
// The same pattern used when handling a unique_ptr... std::unique_ptr<Foo> foo = TryAllocateFoo(); if (foo != nullptr) { foo->DoBar(); // use the value object } // ...or an optional value... std::optional<Foo> foo = MaybeFindFoo(); if (foo.has_value()) { foo->DoBar(); } // ...is also ideal for handling a StatusOr. absl::StatusOr<Foo> foo = TryCreateFoo(); if (foo.ok()) { foo->DoBar(); }
You can limit the scope of a StatusOr
by declaring it in the initializer of
the if
statement and checking ok()
in the condition. If using a StatusOr
immediately, you generally should limit scope in this way (see
Tip #165):
if (absl::StatusOr<Foo> foo = TryCreateFoo(); foo.ok()) { foo->DoBar(); }
StatusOr
The absl::StatusOr<T>
class is a
tagged union with
value semantics indicating
exactly one of the following situations:
T
is available,absl::Status
error (!ok()
) indicating why the value is not present.You can read more about absl::Status
and absl::StatusOr
in
Tip #76.
Treating the StatusOr
object as one would treat a smart pointer helps code
achieve clarity while remaining safe and efficient. Below, we will consider some
of the other ways you might have seen a StatusOr
accessed, and why we prefer
the approach using the indirection operators.
What about absl::StatusOr<T>::value()
?
absl::StatusOr<Foo> foo = TryCreateFoo(); foo.value().DoBar(); // Behavior depends on the build mode.
Here, the behavior depends on the build mode – in particular, whether the code was compiled with exceptions enabled.1 As such, it is not clear to readers if an error status will terminate the program.
The value()
method combines two actions: a test for validity followed by an
access of the value. It should therefore be used only if both actions are
intended (and even then, think twice and consider that its behavior depends on
the build mode). If the status is already known to be OK
, then your ideal
accessor has semantics of simply accessing the value, which is exactly what
operator*
and operator->
provide. In addition to making the code more
precisely indicate your intent, the access will be at least as efficient as
value()
’s contract to test for validity and then access the value.
Treating absl::StatusOr
objects as we would a smart pointer or optional
value also allows us to avoid the conceptually awkward situation of having two
variables referring to the same value. It also avoids the naming dilemmas and
auto
overuse which come with this territory.
// Without digging up the declaration of TryCreateFoo(), a reader will not // immediately understand the types here (optional? pointer? StatusOr?). auto maybe_foo = TryCreateFoo(); // ...compounded by the use of implicit bool rather than `.ok()`. if (!maybe_foo) { /* handle foo not present */ } // Now two variables (maybe_foo, foo) represent the same value. Foo& foo = maybe_foo.value();
_or
SuffixAnother benefit of using a StatusOr
variable’s intrinsic value type after
checking for validity (rather than creating multiple variables for the same
value) is that we can use the best name for the StatusOr
without the need (or
temptation!) to add a prefix or suffix.
There is an analogy here to the
avoidance of the “_ptr
”
suffix when naming pointer variables.
// The type already describes that this is a unique_ptr; `foo` would be fine. std::unique_ptr<Foo> foo_ptr; absl::StatusOr<Foo> foo_or = MaybeFoo(); if (foo_or.ok()) { const Foo& foo = foo_or.value(); foo.DoBar(); }
If there is only one variable (the StatusOr
, avoiding a second named variable
for the unwrapped value), we can drop the suffix, simply naming the variable
after its underlying value (just as we do with pointers).
const absl::StatusOr<Foo> foo = MaybeFoo(); if (foo.ok()) { MakeUseOf(*foo); foo->DoBar(); }
absl::StatusOr
We might write code to move from the T
of an absl::StatusOr<T>
:
absl::StatusOr<Foo> foo = MaybeFoo(); if (foo.ok()) { ConsumeFoo(std::move(*foo)); }
It’s a little better, though, to move from the StatusOr
itself, indicating to
readers (both humans and machines) that the entire StatusOr
object should not
be used after its value has been moved from:
absl::StatusOr<Foo> foo = MaybeFoo(); if (foo.ok()) { ConsumeFoo(*std::move(foo)); }
Testing the absl::StatusOr
object for validity (as you would a smart pointer
or optional
) and accessing it using operator*
or operator->
is readable,
efficient, and safe.
It helps you avoid the potential pitfalls of naming ambiguity mentioned above, and does it all without the use of any macros.
Code that accesses values via operator*
or operator->
(whether using a
pointer, a StatusOr
, an optional
, or otherwise) must first verify that the
value is present. This validation should be close to where the value is accessed
so that readers can easily verify that it is present and correct.
Per the
documentation
of the value()
function, if exceptions are enabled, this will throw
absl::BadStatusOrAccess
(which may be caught, meaning the program may
not terminate). If compiled without exceptions, code will crash with
LOG(FATAL)
. ↩