pw_result#
Pigweed AI summary: The pw::Result<T> class template is used to return either a pw::Status error or an object of type T. Its implementation is based on Abseil's StatusOr<T> class, with a few differences such as using pw::Status instead of absl::Status and supporting constexpr functions. Usage of pw::Result<T> is identical to Abseil's absl::StatusOr<T>. The module also includes the pw::expected type, which is either an alias for std::expected or
pw::Result<T>
is a class template for use in returning either a
pw::Status
error or an object of type T
.
pw::Result<T>
’s implementation is closely based on Abseil’s StatusOr<T>
class.
There are a few differences:
pw::Result<T>
usespw::Status
, which is much less sophisticated thanabsl::Status
.pw::Result<T>
’s functions areconstexpr
andpw::Result<T>
may be used inconstexpr
statements ifT
is trivially destructible.
Usage#
Pigweed AI summary: The usage of `pw::Result<T>` is similar to Abseil's `absl::StatusOr<T>`. It is returned from a function that may return `pw::OkStatus()` and a value or an error status and no value. If `ok()` is true, the `pw::Result<T>` contains a valid `T`. Otherwise, it does not contain a `T` and attempting to access the value is an error. `pw::Result<T>` can be used
Usage of pw::Result<T>
is identical to Abseil’s absl::StatusOr<T>
.
See Abseil’s documentation and
usage tips for guidance.
pw::Result<T>
is returned from a function that may return pw::OkStatus()
and a value or an error status and no value. If ok()
is true, the
pw::Result<T>
contains a valid T
. Otherwise, it does not contain a T
and attempting to access the value is an error.
pw::Result<T>
can be used to directly access the contained type:
#include "pw_result/result.h"
if (pw::Result<Foo> foo = TryCreateFoo(); foo.ok()) {
foo->DoBar();
}
pw::Result
is compatible with PW_TRY
and PW_TRY_ASSIGN
, for example:
#include "pw_status/try.h"
#include "pw_result/result.h"
pw::Result<int> GetAnswer(); // Example function.
pw::Status UseAnswer() {
const pw::Result<int> answer = GetAnswer();
if (!answer.ok()) {
return answer.status();
}
if (answer.value() == 42) {
WhatWasTheUltimateQuestion();
}
return pw::OkStatus();
}
pw::Status UseAnswerWithTry() {
const pw::Result<int> answer = GetAnswer();
PW_TRY(answer.status());
if (answer.value() == 42) {
WhatWasTheUltimateQuestion();
}
return pw::OkStatus();
}
pw::Status UseAnswerWithTryAssign() {
PW_TRY_ASSIGN(const int answer, GetAnswer());
if (answer == 42) {
WhatWasTheUltimateQuestion();
}
return pw::OkStatus();
}
Warning
Be careful not to use larger types by value as this can quickly consume unnecessary stack.
Warning
This module is experimental. Its impact on code size and stack usage has not yet been profiled. Use at your own risk.
Monadic Operations#
Pigweed AI summary: The <literal>pw::Result<T></literal> supports monadic operations, which allow functions to be applied to a <literal>pw::Result<T></literal> that would perform additional computation. These operations do not incur any additional FLASH or RAM cost compared to a traditional if/else ladder. The monadic methods for <literal>and_then</literal> and <literal>transform</literal> are similar, but <literal>and_then</literal> requires
pw::Result<T>
also supports monadic operations, similar to the additions
made to std::optional<T>
in C++23. These operations allow functions to be
applied to a pw::Result<T>
that would perform additional computation.
These operations do not incur any additional FLASH or RAM cost compared to a traditional if/else ladder, as can be seen in the Size report.
// Without monads
pw::Result<Image> GetCuteCat(const Image& img) {
pw::Result<Image> cropped = CropToCat(img);
if (!cropped.ok()) {
return cropped.status();
}
pw::Result<Image> with_tie = AddBowTie(*cropped);
if (!with_tie.ok()) {
return with_tie.status();
}
pw::Result<Image> with_sparkles = MakeEyesSparkle(*with_tie);
if (!with_sparkles.ok()) {
return with_parkes.status();
}
return AddRainbow(MakeSmaller(*with_sparkles));
}
// With monads
pw::Result<Image> GetCuteCat(const Image& img) {
return CropToCat(img)
.and_then(AddBoeTie)
.and_then(MakeEyesSparkle)
.transform(MakeSmaller)
.transform(AddRainbow);
}
pw::Result<T>::and_then
#
Pigweed AI summary: The pw::Result<T>::and_then function returns the result of a provided function on the contained value if it exists, otherwise it returns the contained status in a pw::Result<U>. The function is demonstrated in a code block with an example of creating a Result<Bar> from a Result<Foo>.
The pw::Result<T>::and_then
member function will return the result of the
invocation of the provided function on the contained value if it exists.
Otherwise, returns the contained status in a pw::Result<U>
, which is the
return type of provided function.
// Expositional prototype of and_then:
template <typename T>
class Result {
template <typename U>
Result<U> and_then(Function<Result<U>(T)> func);
};
Result<Foo> CreateFoo();
Result<Bar> CreateBarFromFoo(const Foo& foo);
Result<Bar> bar = CreateFoo().and_then(CreateBarFromFoo);
pw::Result<T>::or_else
#
Pigweed AI summary: The pw::Result<T>::or_else function returns *this if it contains a value, otherwise it returns the result of a provided function that must return a type convertible to pw::Result<T> or void. This is useful for error handling and providing expensive default values. The article provides examples of using or_else in code.
The pw::Result<T>::or_else
member function will return *this
if it
contains a value. Otherwise, it will return the result of the provided function.
The function must return a type convertible to a pw::Result<T>
or void
.
This is particularly useful for handling errors.
// Expositional prototype of or_else:
template <typename T>
class Result {
template <typename U>
requires std::is_convertible_v<U, Result<T>>
Result<T> or_else(Function<U(Status)> func);
Result<T> or_else(Function<void(Status)> func);
};
// Without or_else:
Result<Image> GetCuteCat(const Image& image) {
Result<Image> cropped = CropToCat(image);
if (!cropped.ok()) {
PW_LOG_ERROR("Failed to crop cat: %d", cropped.status().code());
return cropped.status();
}
return cropped;
}
// With or_else:
Result<Image> GetCuteCat(const Image& image) {
return CropToCat(image).or_else(
[](Status s) { PW_LOG_ERROR("Failed to crop cat: %d", s.code()); });
}
Another useful scenario for pw::Result<T>::or_else
is providing a default
value that is expensive to compute. Typically, default values are provided by
using pw::Result<T>::value_or
, but that requires the default value to be
constructed regardless of whether we actually need it.
// With value_or:
Image GetCuteCat(const Image& image) {
// GenerateCuteCat() must execute regardless of the success of CropToCat
return CropToCat(image).value_or(GenerateCuteCat());
}
// With or_else:
Image GetCuteCat(const Image& image) {
// GenerateCuteCat() only executes if CropToCat fails.
return *CropToCat(image).or_else([](Status) { return GenerateCuteCat(); });
}
pw::Result<T>::transform
#
Pigweed AI summary: The pw::Result<T>::transform method returns a pw::Result<U> containing the result of a given function if the original pw::Result<T> contains a value. If not, it returns a pw::Result<U> with the same pw::Status value. The and_then and transform methods are similar, but and_then requires the provided function to return a pw::Result while transform can return any type. If a function that returns a pw::Result is provided to transform, it will return a
The pw::Result<T>::transform
member method will return a pw::Result<U>
which contains the result of the invocation of the given function if *this
contains a value. Otherwise, it returns a pw::Result<U>
with the same
pw::Status
value as *this
.
The monadic methods for and_then
and transform
are fairly similar. The
primary difference is that and_then
requires the provided function to return
a pw::Result
, whereas transform
functions can return any type. Users
should be aware that if they provide a function that returns a pw::Result
to
transform
, this will return a pw::Result<pw::Result<U>>
.
// Expositional prototype of transform:
template <typename T>
class Result {
template <typename U>
Result<U> transform(Function<U(T)> func);
};
Result<int> ConvertStringToInteger(std::string_view);
int MultiplyByTwo(int x);
Result<int> x = ConvertStringToInteger("42")
.transform(MultiplyByTwo);
pw::expected#
Pigweed AI summary: The module includes the pw::expected type, which can be an alias for std::expected or a polyfill if std::expected is not available. This type is similar to pw::Result in that it returns either a type T or an error, but the error can be any type E, not just pw::Status. The PW_TRY and PW_TRY_ASSIGN macros do not work with pw::expected, but it can be used in any place where std::expected from the C++23 standard could
This module also includes the pw::expected
type, which is either an alias
for std::expected
or a polyfill for that type if it is not available. This
type has a similar use case to pw::Result
, in that it either returns a type
T
or an error, but the error may be any type E
, not just pw::Status
.
The PW_TRY
and PW_TRY_ASSIGN
macros do not work with pw::expected
but it should be usable in any place that std::expected
from the C++23
standard could be used.
Size report#
Pigweed AI summary: This section provides a table that compares the size difference between functions returning a Status with an output pointer and functions returning a Result in various situations. The examples are simplified and may not reflect real code usage, so it is recommended to run your own size reports to determine if Result is suitable for your needs. The table shows that there is no difference in size between the two types of functions in the given situations.
The table below showcases the difference in size between functions returning a Status with an output pointer, and functions returning a Result, in various situations.
Note that these are simplified examples which do not necessarily reflect the usage of Result in real code. Make sure to always run your own size reports to check if Result is suitable for you.
Label |
Segment |
Delta |
---|---|---|
Simple function |
(ALL) |
0 |
Simple function without inlining |
(ALL) |
0 |
Returning a larger object (std::span) |
(ALL) |
0 |
Using and_then instead of if ladder |
(ALL) |
0 |
Using or_else instead of if ladder |
(ALL) |
0 |
Using transform instead of if ladder |
(ALL) |
0 |
Zephyr#
Pigweed AI summary: To enable pw_result for Zephyr, the configuration of the project needs to have CONFIG_PIGWEED_RESULT set to "y".
To enable pw_result
for Zephyr add CONFIG_PIGWEED_RESULT=y
to the
project’s configuration.