1.. _module-pw_result: 2 3========= 4pw_result 5========= 6.. pigweed-module:: 7 :name: pw_result 8 9 - **Easy**: Minimal boilerplate via with ``PW_TRY_ASSIGN`` macro and chaining 10 - **Reliable**: Propagate errors consistently for less bugs 11 - **Battle-tested**: Just like ``absl::StatusOr``, deployed extensively 12 13``pw::Result<T>`` is a union of an error (:cpp:class:`pw::Status`) and a value 14(``T``), in a type-safe and convenient wrapper. This enables operations to 15return either a value on success, or an error code on failure, in one type. 16Propagating errors with this one type is less burdensome than other methods, 17reducing the temptation to write code that crashes or fails silently in error 18cases. Take the following lengthy code for example: 19 20.. code-block:: cpp 21 22 #include "pw_log/log.h" 23 #include "pw_result/result.h" 24 #include "pw_status/try.h" 25 26 pw::Result<int> GetBatteryVoltageMillivolts(); // Can fail 27 28 pw::Status UpdateChargerDisplay() { 29 const pw::Result<int> battery_mv = GetBatteryVoltageMillivolts(); 30 if (!battery_mv.ok()) { 31 // Voltage read failed; propagate the status to callers. 32 return battery_mv.status(); 33 } 34 PW_LOG_INFO("Battery voltage: %d mV", *battery_mv); 35 SetDisplayBatteryVoltage(*battery_mv); 36 return pw::OkStatus(); 37 } 38 39The ``PW_TRY_ASSIGN`` macro enables shortening the above to: 40 41.. code-block:: cpp 42 43 pw::Status UpdateChargerDisplay() { 44 PW_TRY_ASSIGN(const int battery_mv, GetBatteryVoltageMillivolts()); 45 PW_LOG_INFO("Battery voltage: %d mV", battery_mv); 46 SetDisplayBatteryVoltage(battery_mv); 47 return pw::OkStatus(); 48 } 49 50The ``pw::Result<T>`` class is based on Abseil's ``absl::StatusOr<T>`` class. 51See Abseil's `documentation 52<https://abseil.io/docs/cpp/guides/status#returning-a-status-or-a-value>`_ and 53`usage tips <https://abseil.io/tips/181>`_ for extra guidance. 54 55----------- 56Get started 57----------- 58To deploy ``pw_result``, depend on the library: 59 60.. tab-set:: 61 62 .. tab-item:: Bazel 63 64 Add ``@pigweed//pw_result`` to the ``deps`` list in your Bazel target: 65 66 .. code-block:: 67 68 cc_library("...") { 69 # ... 70 deps = [ 71 # ... 72 "@pigweed//pw_result", 73 # ... 74 ] 75 } 76 77 This assumes that your Bazel ``WORKSPACE`` has a `repository 78 <https://bazel.build/concepts/build-ref#repositories>`_ named ``@pigweed`` 79 that points to the upstream Pigweed repository. 80 81 .. tab-item:: GN 82 83 Add ``$dir_pw_result`` to the ``deps`` list in your ``pw_executable()`` 84 build target: 85 86 .. code-block:: 87 88 pw_executable("...") { 89 # ... 90 deps = [ 91 # ... 92 "$dir_pw_result", 93 # ... 94 ] 95 } 96 97 .. tab-item:: CMake 98 99 Add ``pw_result`` to your ``pw_add_library`` or similar CMake target: 100 101 .. code-block:: 102 103 pw_add_library(my_library STATIC 104 HEADERS 105 ... 106 PRIVATE_DEPS 107 # ... 108 pw_result 109 # ... 110 ) 111 112 .. tab-item:: Zephyr 113 114 There are two ways to use ``pw_result`` from a Zephyr project: 115 116 * Depend on ``pw_result`` in your CMake target (see CMake tab). This is 117 the Pigweed team's suggested approach since it enables precise CMake 118 dependency analysis. 119 120 * Add ``CONFIG_PIGWEED_RESULT=y`` to the Zephyr project's configuration, 121 which causes ``pw_result`` to become a global dependency and have the 122 includes exposed to all targets. The Pigweed team does not recommend 123 this approach, though it is the typical Zephyr solution. 124 125------ 126Guides 127------ 128 129Overview 130======== 131``pw::Result<T>`` objects represent either: 132 133* A value (accessed with ``operator*`` or ``operator->``) and an OK status 134* An error (accessed with ``.status()``) and no value 135 136If ``result.ok()`` returns ``true`` the instance contains a valid value (of type 137``T``). Otherwise, it does not contain a valid value, and attempting to access 138the value is an error. 139 140A ``pw::Result<T>`` instance can never contain both an OK status and a value. In 141most cases, this is the appropriate choice. For cases where both a size and a 142status must be returned, see :ref:`module-pw_status-guide-status-with-size`. 143 144.. warning:: 145 146 Be careful not to use large value types in ``pw::Result`` objects, since they 147 are embedded by value. This can quickly consume stack. 148 149Returning a result from a function 150================================== 151To return a result from a function, return either a value (implicitly indicating 152success), or a `non-OK pw::Status <module-pw_status-codes>` object otherwise (to 153indicate failure). 154 155.. code-block:: cpp 156 157 pw::Result<float> GetAirPressureSensor() { 158 uint32_t sensor_value; 159 if (!vendor_raw_sensor_read(&sensor_value)) { 160 return pw::Status::Unavailable(); // Converted to error Result 161 } 162 return sensor_value / 216.5; // Converted to OK Result with float 163 } 164 165Accessing the contained value 166============================= 167Accessing the value held by a ``pw::Result<T>`` should be performed via 168``operator*`` or ``operator->``, after a call to ``ok()`` has verified that the 169value is present. 170 171Accessing the value via ``operator->`` avoids introducing an extra local 172variable to capture the result. 173 174.. code-block:: cpp 175 176 #include "pw_result/result.h" 177 178 void Example() { 179 if (pw::Result<Foo> foo = TryCreateFoo(); foo.ok()) { 180 foo->DoBar(); // Note direct access to value inside the Result 181 } 182 } 183 184Propagating errors from results 185=============================== 186``pw::Result`` instances are compatible with the ``PW_TRY`` and 187``PW_TRY_ASSIGN`` macros, which enable concise propagation of errors for 188falliable operations that return values: 189 190.. code-block:: cpp 191 192 #include "pw_status/try.h" 193 #include "pw_result/result.h" 194 195 pw::Result<int> GetAnswer(); // Example function. 196 197 pw::Status UseAnswerWithTry() { 198 const pw::Result<int> answer = GetAnswer(); 199 PW_TRY(answer.status()); 200 if (answer.value() == 42) { 201 WhatWasTheUltimateQuestion(); 202 } 203 return pw::OkStatus(); 204 } 205 206With the ``PW_TRY_ASSIGN`` macro, you can combine declaring the result with the 207return: 208 209.. code-block:: cpp 210 211 pw::Status UseAnswerWithTryAssign() { 212 PW_TRY_ASSIGN(const int answer, GetAnswer()); 213 PW_LOG_INFO("Got answer: %d", static_cast<int>(answer)); 214 return pw::OkStatus(); 215 } 216 217Chaining results 218================ 219``pw::Result<T>`` also supports chained operations, similar to the additions 220made to ``std::optional<T>`` in C++23. These operations allow functions to be 221applied to a ``pw::Result<T>`` that would perform additional computation. 222 223These operations do not incur any additional FLASH or RAM cost compared to a 224traditional if/else ladder, as can be seen in the `Code size analysis`_. 225 226Without chaining or ``PW_TRY_ASSIGN``, invoking multiple falliable operations is 227verbose: 228 229.. code-block:: cpp 230 231 pw::Result<Image> GetCuteCat(const Image& img) { 232 pw::Result<Image> cropped = CropToCat(img); 233 if (!cropped.ok()) { 234 return cropped.status(); 235 } 236 pw::Result<Image> with_tie = AddBowTie(*cropped); 237 if (!with_tie.ok()) { 238 return with_tie.status(); 239 } 240 pw::Result<Image> with_sparkles = MakeEyesSparkle(*with_tie); 241 if (!with_sparkles.ok()) { 242 return with_parkes.status(); 243 } 244 return AddRainbow(MakeSmaller(*with_sparkles)); 245 } 246 247Leveraging ``PW_TRY_ASSIGN`` reduces the verbosity: 248 249.. code-block:: cpp 250 251 // Without chaining but using PW_TRY_ASSIGN. 252 pw::Result<Image> GetCuteCat(const Image& img) { 253 PW_TRY_ASSIGN(Image cropped, CropToCat(img)); 254 PW_TRY_ASSIGN(Image with_tie, AddBowTie(*cropped)); 255 PW_TRY_ASSIGN(Image with_sparkles, MakeEyesSparkle(*with_tie)); 256 return AddRainbow(MakeSmaller(*with_sparkles)); 257 } 258 259With chaining we can reduce the code even further: 260 261.. code-block:: cpp 262 263 pw::Result<Image> GetCuteCat(const Image& img) { 264 return CropToCat(img) 265 .and_then(AddBoeTie) 266 .and_then(MakeEyesSparkle) 267 .transform(MakeSmaller) 268 .transform(AddRainbow); 269 } 270 271``pw::Result<T>::and_then`` 272--------------------------- 273The ``pw::Result<T>::and_then`` member function will return the result of the 274invocation of the provided function on the contained value if it exists. 275Otherwise, returns the contained status in a ``pw::Result<U>``, which is the 276return type of provided function. 277 278.. code-block:: cpp 279 280 // Expositional prototype of and_then: 281 template <typename T> 282 class Result { 283 template <typename U> 284 Result<U> and_then(Function<Result<U>(T)> func); 285 }; 286 287 Result<Foo> CreateFoo(); 288 Result<Bar> CreateBarFromFoo(const Foo& foo); 289 290 Result<Bar> bar = CreateFoo().and_then(CreateBarFromFoo); 291 292``pw::Result<T>::or_else`` 293-------------------------- 294The ``pw::Result<T>::or_else`` member function will return ``*this`` if it 295contains a value. Otherwise, it will return the result of the provided function. 296The function must return a type convertible to a ``pw::Result<T>`` or ``void``. 297This is particularly useful for handling errors. 298 299.. code-block:: cpp 300 301 // Expositional prototype of or_else: 302 template <typename T> 303 class Result { 304 template <typename U> 305 requires std::is_convertible_v<U, Result<T>> 306 Result<T> or_else(Function<U(Status)> func); 307 308 Result<T> or_else(Function<void(Status)> func); 309 }; 310 311 // Without or_else: 312 Result<Image> GetCuteCat(const Image& image) { 313 Result<Image> cropped = CropToCat(image); 314 if (!cropped.ok()) { 315 PW_LOG_ERROR("Failed to crop cat: %d", cropped.status().code()); 316 return cropped.status(); 317 } 318 return cropped; 319 } 320 321 // With or_else: 322 Result<Image> GetCuteCat(const Image& image) { 323 return CropToCat(image).or_else( 324 [](Status s) { PW_LOG_ERROR("Failed to crop cat: %d", s.code()); }); 325 } 326 327Another useful scenario for ``pw::Result<T>::or_else`` is providing a default 328value that is expensive to compute. Typically, default values are provided by 329using ``pw::Result<T>::value_or``, but that requires the default value to be 330constructed regardless of whether you actually need it. 331 332.. code-block:: cpp 333 334 // With value_or: 335 Image GetCuteCat(const Image& image) { 336 // GenerateCuteCat() must execute regardless of the success of CropToCat 337 return CropToCat(image).value_or(GenerateCuteCat()); 338 } 339 340 // With or_else: 341 Image GetCuteCat(const Image& image) { 342 // GenerateCuteCat() only executes if CropToCat fails. 343 return *CropToCat(image).or_else([](Status) { return GenerateCuteCat(); }); 344 } 345 346``pw::Result<T>::transform`` 347---------------------------- 348The ``pw::Result<T>::transform`` member method will return a ``pw::Result<U>`` 349which contains the result of the invocation of the given function if ``*this`` 350contains a value. Otherwise, it returns a ``pw::Result<U>`` with the same 351``pw::Status`` value as ``*this``. 352 353The monadic methods for ``and_then`` and ``transform`` are fairly similar. The 354primary difference is that ``and_then`` requires the provided function to return 355a ``pw::Result``, whereas ``transform`` functions can return any type. Users 356should be aware that if they provide a function that returns a ``pw::Result`` to 357``transform``, this will return a ``pw::Result<pw::Result<U>>``. 358 359.. code-block:: cpp 360 361 // Expositional prototype of transform: 362 template <typename T> 363 class Result { 364 template <typename U> 365 Result<U> transform(Function<U(T)> func); 366 }; 367 368 Result<int> ConvertStringToInteger(std::string_view); 369 int MultiplyByTwo(int x); 370 371 Result<int> x = ConvertStringToInteger("42") 372 .transform(MultiplyByTwo); 373 374Results with custom error types: ``pw::expected`` 375================================================= 376Most error codes can fit into one of the status codes supported by 377``pw::Status``. However, custom error codes are occasionally needed for 378interfacing with other libraries, or other special situations. This module 379includes the ``pw::expected`` type for these situtaions. 380 381``pw::expected`` is either an alias for ``std::expected`` or a polyfill for that 382type if it is not available. This type has a similar use case to ``pw::Result``, 383in that it either returns a type ``T`` or an error, but the error may be any 384type ``E``, not just ``pw::Status``. The ``PW_TRY`` and ``PW_TRY_ASSIGN`` 385macros do not work with ``pw::expected`` but it should be usable in any place 386that ``std::expected`` from the ``C++23`` standard could be used. 387 388.. code-block:: cpp 389 390 #include "pw_result/expected.h" 391 392 pw::expected<float, const char*> ReadBatteryVoltageOrError(); 393 394 void TrySensorRead() { 395 pw::expected<float, const char*> voltage = ReadBatteryVoltageOrError(); 396 if (!voltage.has_value()) { 397 PW_LOG_ERROR("Couldn't read battery: %s", voltage.error()); 398 return; 399 } 400 PW_LOG_ERROR("Battery: %f", voltage.value()); 401 } 402 403For more information, see the `standard library reference 404<https://en.cppreference.com/w/cpp/utility/expected>`_. 405 406------ 407Design 408------ 409.. inclusive-language: disable 410 411``pw::Result<T>``'s implementation is closely based on Abseil's `StatusOr<T> 412class <https://github.com/abseil/abseil-cpp/blob/master/absl/status/statusor.h>`_. 413There are a few differences: 414 415.. inclusive-language: enable 416 417* ``pw::Result<T>`` objects represent their status code with a ``pw::Status`` 418 member rather than an ``absl::Status``. The ``pw::Status`` objects are less 419 sophisticated but smaller than ``absl::Status`` objects. In particular, 420 ``pw::Status`` objects do not support string errors, and are limited to the 421 canonical error codes. 422* ``pw::Result<T>`` objects are usable in ``constexpr`` statements if the value 423 type ``T`` is trivially destructible. 424 425------- 426Roadmap 427------- 428This module is stable. 429 430------------------ 431Code size analysis 432------------------ 433The table below showcases the difference in size between functions returning a 434``pw::Status`` with an output pointer, and functions returning a Result, in 435various situations. 436 437Note that these are simplified examples which do not necessarily reflect the 438usage of ``pw::Result`` in real code. Make sure to always run your own size 439reports to check if ``pw::Result`` is suitable for you. 440 441.. include:: result_size 442