Get started & guides#
pw_status: Exception-free error propagation for embedded
Get started#
To deploy pw_status, depend on the library:
Add @pigweed//pw_status to the deps list in your Bazel target:
cc_library("...") {
# ...
deps = [
# ...
"@pigweed//pw_status",
# ...
]
}
Add $dir_pw_status to the deps list in your pw_executable()
build target:
pw_executable("...") {
# ...
deps = [
# ...
"$dir_pw_status",
# ...
]
}
Add pw_status to your pw_add_library or similar CMake target:
pw_add_library(my_library STATIC
HEADERS
...
PRIVATE_DEPS
# ...
pw_status
# ...
)
There are two ways to use pw_status from a Zephyr project:
Depend on
pw_statusin your CMake target (see CMake tab). This is the Pigweed team’s suggested approach since it enables precise CMake dependency analysis.Add
CONFIG_PIGWEED_STATUS=yto the Zephyr project’s configuration, which causespw_statusto become a global dependency and have the includes exposed to all targets. The Pigweed team does not recommend this approach, though it is the typical Zephyr solution.
Then use the status object or try macros:
#include "pw_status/status.h"
#include "pw_status/try.h"
pw::Status MyOperation() {
PW_TRY(SubOp1());
PW_TRY(SubOp2());
// ...
return pw::OkStatus();
}
Guides#
Tracking the first error encountered#
In some contexts it is useful to track the first error encountered while
allowing execution to continue. Manually writing out if statements to check
and then assign quickly becomes verbose, and doesn’t explicitly highlight the
intended behavior of “latching” to the first error.
No: Track status manually across calls
Status overall_status;
for (Sector& sector : sectors) {
Status erase_status = sector.Erase();
if (!overall_status.ok()) {
overall_status = erase_status;
}
if (erase_status.ok()) {
Status header_write_status = sector.WriteHeader();
if (!overall_status.ok()) {
overall_status = header_write_status;
}
}
}
return overall_status;
pw::Status has a pw::Status::Update helper function that does exactly this to reduce visual clutter and succinctly highlight the intended behavior.
Yes: Track status with pw::Status::Update
Status overall_status;
for (Sector& sector : sectors) {
Status erase_status = sector.Erase();
overall_status.Update(erase_status);
if (erase_status.ok()) {
overall_status.Update(sector.WriteHeader());
}
}
return overall_status;
Defining custom status types#
Many drivers and subsystems have their own set of error codes. Rather than
mapping them onto the built-in pw::Status codes, define a custom status
type that wraps the domain-specific enum while retaining status semantics:
ok() checks, [[nodiscard]] enforcement, and error propagation with
PW_TRY and pw::Result.
The recommended way to define a custom status type is with
pw_enum’s PW_ENUM_STATUS macro. Declare the status code enum
in a pw_cc_enum_library header, then register it with PW_ENUM_STATUS.
The first argument is the fully qualified name of the status class to
generate. The second argument is the enumerator that represents success,
including its enum type (e.g. I2cStatusCode::kOk); its namespace is
inferred from the status class if omitted.
#include <cstdint>
#include "pw_enum/generate.h"
namespace my_driver {
enum class I2cStatusCode : uint8_t {
kOk,
kNack,
kBusError,
kTimeout,
};
} // namespace my_driver
// Generates the my_driver::I2cStatus class. The second argument is the OK
// enumerator (its namespace is inferred from the status class).
PW_ENUM_STATUS(
my_driver::I2cStatus, I2cStatusCode::kOk, kOk, kNack, kBusError, kTimeout);
pw_cc_enum_library(
name = "i2c_status",
enum_headers = ["private/enum_example/i2c_status.h"],
strip_include_prefix = "private",
)
This generates a [[nodiscard]] class named I2cStatus that inherits from
pw::StatusBase, with:
Access to the underlying status code enum via
I2cStatus::Code(e.g.I2cStatus::Code::kNack).Static factory functions for non-OK
kEnumName-style enumerators (e.g.I2cStatus::Nack(),I2cStatus::Timeout()). No factory is generated for the OK code; default construct (I2cStatus()) for OK.Is*()query functions (e.g.status.IsNack()).IsOk()is omitted for codes named OK, sinceok()serves that purpose.Explicit construction from
I2cStatus::Code(e.g.I2cStatus(I2cStatus::Code::kNack)).Stringification via
.str()and pw::EnumToString().
I2cStatus WriteByte(uint8_t address, uint8_t byte) {
if (address == 0) {
return I2cStatus::Nack(); // Factory for I2cStatusCode::kNack
}
if (byte == 0xFF) {
return I2cStatus(I2cStatus::Code::kBusError); // Explicit construction
}
return I2cStatus(); // Default constructed as OK
}
I2cStatus WritePacket(uint8_t address, uint8_t header, uint8_t payload) {
PW_TRY(WriteByte(address, header)); // Returns early on error
PW_TRY(WriteByte(address, payload));
return I2cStatus();
}
bool ShouldRetry(I2cStatus status) {
return status.IsNack() || status.IsTimeout(); // Query the code
}
See pw_enum for details.
Alternative: pw::StatusOf#
If the status code enum cannot be declared in a pw_cc_enum_library,
pw::StatusOf wraps an existing enum in a pw::StatusBase-derived
class. Create a type alias of pw::StatusOf parameterized on the OK
enumerator. It does not generate factory functions, and str() requires the
enum to support pw::EnumToString() (e.g. by registering it with
PW_ENUM).
enum class DriverError : uint8_t {
kOk,
kBusFault,
kTimeout,
};
using DriverStatus = pw::StatusOf<DriverError::kOk>;
constexpr DriverStatus WriteByte(uint8_t byte) {
if (byte == 0xFF) {
return DriverStatus(DriverError::kBusFault);
}
return DriverStatus();
}
// Propagate custom errors using PW_TRY.
constexpr DriverStatus SendPacket(uint8_t header, uint8_t payload) {
PW_TRY(WriteByte(header));
PW_TRY(WriteByte(payload));
return DriverStatus();
}
Alternative: Manual definition#
To add domain-specific member functions, subclass pw::StatusBase
directly. Pass the derived class and the OK code as template arguments. Declare
the class [[nodiscard]] and define constructors that forward to
StatusBase, whose constructors are protected.
enum class SensorError : uint8_t {
kOk,
kBusy,
kCalibrationFailed,
};
class [[nodiscard]] SensorStatus final
: public pw::StatusBase<SensorStatus, SensorError::kOk> {
public:
constexpr SensorStatus() = default;
explicit constexpr SensorStatus(Code code) : StatusBase(code) {}
// Subclasses may add domain-specific helpers.
[[nodiscard]] constexpr bool IsRetryable() const {
return code() == Code::kBusy;
}
};
Jointly reporting status with size#
pw::StatusWithSize (pw_status/status_with_size.h) is a
convenient, efficient class for reporting a status along with an unsigned
integer value. It is similar to the pw::Result<T> class, but it stores both
a size and a status, regardless of the status value, and only supports a limited
range (27 bits).
pw::StatusWithSize values may be created with functions similar to
pw::Status. For example:
#include "pw_status/status_with_size.h"
// An OK StatusWithSize with a size of 123.
StatusWithSize(123);
// A NOT_FOUND StatusWithSize with a size of 0.
StatusWithSize::NotFound();
// A RESOURCE_EXHAUSTED StatusWithSize with a size of 10.
StatusWithSize::ResourceExhausted(10);
pw::StatusWithSize is useful for cases where an operation may partially
complete - for example read operations may read some number of bytes into an
output buffer, but not all.
Reducing error handling boilerplate#
Manual error handling through return codes is easy to understand and
straightforward to write, but leads to verbose code. To reduce boilerplate,
Pigweed has the PW_TRY (pw_status/try.h) macro, easing
development of functions checking or returning pw::Status and
pw::StatusWithSize objects. The PW_TRY and
PW_TRY_WITH_SIZE macros call a function and do an early return if
the function’s return status is not OK.
Example:
Status PwTryExample() {
PW_TRY(FunctionThatReturnsStatus());
PW_TRY(FunctionThatReturnsStatusWithSize());
// Do something, only executed if both functions above return OK.
}
StatusWithSize PwTryWithSizeExample() {
PW_TRY_WITH_SIZE(FunctionThatReturnsStatus());
PW_TRY_WITH_SIZE(FunctionThatReturnsStatusWithSize());
// Do something, only executed if both functions above return OK.
}
PW_TRY_ASSIGN is for working with pw::StatusWithSize objects in
functions that return Status. It is similar to PW_TRY with the addition of
assigning the size from the pw::StatusWithSize on ok.
Status PwTryAssignExample() {
size_t size_value PW_TRY_ASSIGN(size_value,
FunctionThatReturnsStatusWithSize());
// Do something that uses size_value. size_value is only assigned and this
// following code executed if the PW_TRY_ASSIGN function above returns OK.
}