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",
    # ...
  ]
}

This assumes that your Bazel WORKSPACE has a repository named @pigweed that points to the upstream Pigweed repository.

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_status in 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=y to the Zephyr project’s configuration, which causes pw_status to 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/try.h"
#include "pw_status/status.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;

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 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.
}