xref: /aosp_15_r20/external/pigweed/pw_result/docs.rst (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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