| .. _module-pw_result: |
| |
| ========= |
| pw_result |
| ========= |
| .. pigweed-module:: |
| :name: pw_result |
| |
| - **Easy**: Minimal boilerplate via with ``PW_TRY_ASSIGN`` macro and chaining |
| - **Reliable**: Propagate errors consistently for less bugs |
| - **Battle-tested**: Just like ``absl::StatusOr``, deployed extensively |
| |
| ``pw::Result<T>`` is a union of an error (:cpp:class:`pw::Status`) and a value |
| (``T``), in a type-safe and convenient wrapper. This enables operations to |
| return either a value on success, or an error code on failure, in one type. |
| Propagating errors with this one type is less burdensome than other methods, |
| reducing the temptation to write code that crashes or fails silently in error |
| cases. Take the following lengthy code for example: |
| |
| .. code-block:: cpp |
| |
| #include "pw_log/log.h" |
| #include "pw_result/result.h" |
| #include "pw_status/try.h" |
| |
| pw::Result<int> GetBatteryVoltageMillivolts(); // Can fail |
| |
| pw::Status UpdateChargerDisplay() { |
| const pw::Result<int> battery_mv = GetBatteryVoltageMillivolts(); |
| if (!battery_mv.ok()) { |
| // Voltage read failed; propagate the status to callers. |
| return battery_mv.status(); |
| } |
| PW_LOG_INFO("Battery voltage: %d mV", *battery_mv); |
| SetDisplayBatteryVoltage(*battery_mv); |
| return pw::OkStatus(); |
| } |
| |
| The ``PW_TRY_ASSIGN`` macro enables shortening the above to: |
| |
| .. code-block:: cpp |
| |
| pw::Status UpdateChargerDisplay() { |
| PW_TRY_ASSIGN(const int battery_mv, GetBatteryVoltageMillivolts()); |
| PW_LOG_INFO("Battery voltage: %d mV", *battery_mv); |
| SetDisplayBatteryVoltage(*battery_mv); |
| return pw::OkStatus(); |
| } |
| |
| The ``pw::Result<T>`` class is based on Abseil's ``absl::StatusOr<T>`` class. |
| See Abseil's `documentation |
| <https://abseil.io/docs/cpp/guides/status#returning-a-status-or-a-value>`_ and |
| `usage tips <https://abseil.io/tips/181>`_ for extra guidance. |
| |
| ----------- |
| Get started |
| ----------- |
| To deploy ``pw_result``, depend on the library: |
| |
| .. tab-set:: |
| |
| .. tab-item:: Bazel |
| |
| Add ``@pigweed//pw_result`` to the ``deps`` list in your Bazel target: |
| |
| .. code-block:: |
| |
| cc_library("...") { |
| # ... |
| deps = [ |
| # ... |
| "@pigweed//pw_result", |
| # ... |
| ] |
| } |
| |
| This assumes that your Bazel ``WORKSPACE`` has a `repository |
| <https://bazel.build/concepts/build-ref#repositories>`_ named ``@pigweed`` |
| that points to the upstream Pigweed repository. |
| |
| .. tab-item:: GN |
| |
| Add ``$dir_pw_result`` to the ``deps`` list in your ``pw_executable()`` |
| build target: |
| |
| .. code-block:: |
| |
| pw_executable("...") { |
| # ... |
| deps = [ |
| # ... |
| "$dir_pw_result", |
| # ... |
| ] |
| } |
| |
| .. tab-item:: CMake |
| |
| Add ``pw_result`` to your ``pw_add_library`` or similar CMake target: |
| |
| .. code-block:: |
| |
| pw_add_library(my_library STATIC |
| HEADERS |
| ... |
| PRIVATE_DEPS |
| # ... |
| pw_result |
| # ... |
| ) |
| |
| .. tab-item:: Zephyr |
| |
| There are two ways to use ``pw_result`` from a Zephyr project: |
| |
| * Depend on ``pw_result`` 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_RESULT=y`` to the Zephyr project's configuration, |
| which causes ``pw_result`` 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. |
| |
| ------ |
| Guides |
| ------ |
| |
| Overview |
| ======== |
| ``pw::Result<T>`` objects represent either: |
| |
| * A value (accessed with ``operator*`` or ``operator->``) and an OK status |
| * An error (accessed with ``.status()``) and no value |
| |
| If ``result.ok()`` returns ``true`` the instance contains a valid value (of type |
| ``T``). Otherwise, it does not contain a valid value, and attempting to access |
| the value is an error. |
| |
| A ``pw::Result<T>`` instance can never contain both an OK status and a value. In |
| most cases, this is the appropriate choice. For cases where both a size and a |
| status must be returned, see :ref:`module-pw_status-guide-status-with-size`. |
| |
| .. warning:: |
| |
| Be careful not to use large value types in ``pw::Result`` objects, since they |
| are embedded by value. This can quickly consume stack. |
| |
| Returning a result from a function |
| ================================== |
| To return a result from a function, return either a value (implicitly indicating |
| success), or a `non-OK pw::Status <module-pw_status-codes>` object otherwise (to |
| indicate failure). |
| |
| .. code-block:: cpp |
| |
| pw::Result<float> GetAirPressureSensor() { |
| uint32_t sensor_value; |
| if (!vendor_raw_sensor_read(&sensor_value)) { |
| return pw::Status::Unavailable(); // Converted to error Result |
| } |
| return sensor_value / 216.5; // Converted to OK Result with float |
| } |
| |
| Accessing the contained value |
| ============================= |
| Accessing the value held by a ``pw::Result<T>`` should be performed via |
| ``operator*`` or ``operator->``, after a call to ``ok()`` has verified that the |
| value is present. |
| |
| Accessing the value via ``operator->`` avoids introducing an extra local |
| variable to capture the result. |
| |
| .. code-block:: cpp |
| |
| #include "pw_result/result.h" |
| |
| void Example() { |
| if (pw::Result<Foo> foo = TryCreateFoo(); foo.ok()) { |
| foo->DoBar(); // Note direct access to value inside the Result |
| } |
| } |
| |
| Propagating errors from results |
| =============================== |
| ``pw::Result`` instances are compatible with the ``PW_TRY`` and |
| ``PW_TRY_ASSIGN`` macros, which enable concise propagation of errors for |
| falliable operations that return values: |
| |
| .. code-block:: cpp |
| |
| #include "pw_status/try.h" |
| #include "pw_result/result.h" |
| |
| pw::Result<int> GetAnswer(); // Example function. |
| |
| pw::Status UseAnswerWithTry() { |
| const pw::Result<int> answer = GetAnswer(); |
| PW_TRY(answer.status()); |
| if (answer.value() == 42) { |
| WhatWasTheUltimateQuestion(); |
| } |
| return pw::OkStatus(); |
| } |
| |
| With the ``PW_TRY_ASSIGN`` macro, you can combine declaring the result with the |
| return: |
| |
| .. code-block:: cpp |
| |
| pw::Status UseAnswerWithTryAssign() { |
| PW_TRY_ASSIGN(const int answer, GetAnswer()); |
| PW_LOG_INFO("Got answer: %d", static_cast<int>(answer)); |
| return pw::OkStatus(); |
| } |
| |
| Chaining results |
| ================ |
| ``pw::Result<T>`` also supports chained 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 `Code size analysis`_. |
| |
| Without chaining or ``PW_TRY_ASSIGN``, invoking multiple falliable operations is |
| verbose: |
| |
| .. code-block:: cpp |
| |
| 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)); |
| } |
| |
| Leveraging ``PW_TRY_ASSIGN`` reduces the verbosity: |
| |
| .. code-block:: cpp |
| |
| // Without chaining but using PW_TRY_ASSIGN. |
| pw::Result<Image> GetCuteCat(const Image& img) { |
| PW_TRY_ASSIGN(Image cropped, CropToCat(img)); |
| PW_TRY_ASSIGN(Image with_tie, AddBowTie(*cropped)); |
| PW_TRY_ASSIGN(Image with_sparkles, MakeEyesSparkle(*with_tie)); |
| return AddRainbow(MakeSmaller(*with_sparkles)); |
| } |
| |
| With chaining we can reduce the code even further: |
| |
| .. code-block:: cpp |
| |
| 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`` |
| --------------------------- |
| 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. |
| |
| .. code-block:: cpp |
| |
| // 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`` |
| -------------------------- |
| 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. |
| |
| .. code-block:: cpp |
| |
| // 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 you actually need it. |
| |
| .. code-block:: cpp |
| |
| // 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`` |
| ---------------------------- |
| 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>>``. |
| |
| .. code-block:: cpp |
| |
| // 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); |
| |
| Results with custom error types: ``pw::expected`` |
| ================================================= |
| Most error codes can fit into one of the status codes supported by |
| ``pw::Status``. However, custom error codes are occasionally needed for |
| interfacing with other libraries, or other special situations. This module |
| includes the ``pw::expected`` type for these situtaions. |
| |
| ``pw::expected`` 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. |
| |
| .. code-block:: cpp |
| |
| #include "pw_result/expected.h" |
| |
| pw::expected<float, const char*> ReadBatteryVoltageOrError(); |
| |
| void TrySensorRead() { |
| pw::expected<float, const char*> voltage = ReadBatteryVoltageOrError(); |
| if (!voltage.has_value()) { |
| PW_LOG_ERROR("Couldn't read battery: %s", voltage.error()); |
| return; |
| } |
| PW_LOG_ERROR("Battery: %f", voltage.value()); |
| } |
| |
| For more information, see the `standard library reference |
| <https://en.cppreference.com/w/cpp/utility/expected>`_. |
| |
| ------ |
| Design |
| ------ |
| .. inclusive-language: disable |
| |
| ``pw::Result<T>``'s implementation is closely based on Abseil's `StatusOr<T> |
| class <https://github.com/abseil/abseil-cpp/blob/master/absl/status/statusor.h>`_. |
| There are a few differences: |
| |
| .. inclusive-language: enable |
| |
| * ``pw::Result<T>`` objects represent their status code with a ``pw::Status`` |
| member rather than an ``absl::Status``. The ``pw::Status`` objects are less |
| sophisticated but smaller than ``absl::Status`` objects. In particular, |
| ``pw::Status`` objects do not support string errors, and are limited to the |
| canonical error codes. |
| * ``pw::Result<T>`` objects are usable in ``constexpr`` statements if the value |
| type ``T`` is trivially destructible. |
| |
| ------- |
| Roadmap |
| ------- |
| This module is stable. |
| |
| ------------------ |
| Code size analysis |
| ------------------ |
| The table below showcases the difference in size between functions returning a |
| ``pw::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 ``pw::Result`` in real code. Make sure to always run your own size |
| reports to check if ``pw::Result`` is suitable for you. |
| |
| .. include:: result_size |