Bug 1935816 - Make MOZ_TRY() macro return its success value instead of discarding it. r=glandium

This macro allows to write `mozilla::Result`-based code
similar to the rust try macro by using
gcc Statement Expressions [0],
which is a non-standard extension to gcc and clang.
This macro is used as an rvalue, while still returning early for error cases:

```
// Result<SuccessValue, E> Func();
Result<T, E> Bar() {
  SuccessValue val = MOZ_TRY(Func());
  // ...
}
```

This macro can only be used if `SuccessValue` is movable.

[0]: https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html

Differential Revision: https://phabricator.services.mozilla.com/D235415
This commit is contained in:
Jan-Niklas Jaeschke
2025-05-09 09:25:32 +00:00
committed by jjaschke@mozilla.com
parent 9fa68a2225
commit 0a20c29367
2 changed files with 42 additions and 26 deletions

View File

@@ -10,32 +10,37 @@
#include "mozilla/Result.h"
/**
* MOZ_TRY(expr) is the C++ equivalent of Rust's `try!(expr);`. First, it
* evaluates expr, which must produce a Result value. On success, it
* discards the result altogether. On error, it immediately returns an error
* Result from the enclosing function.
* MOZ_TRY(expr) is the C++ equivalent of Rust's `target = try!(expr);`, using
* gcc's statement expressions [0]. First, it evaluates expr, which must produce
* a Result value. On success, the result's success value is 'returned' as
* rvalue. On error, immediately returns the error result. This pattern allows
* to directly assign the success value:
*
* ```
* SuccessValue val = MOZ_TRY(Func());
* ```
*
* Where `Func()` returns a `Result<SuccessValue, E>` and is called in a
* function that returns `Result<T, E>`.
*
* [0]: https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html
*/
#define MOZ_TRY(expr) \
do { \
auto mozTryTempResult_ = ::mozilla::ToResult(expr); \
if (MOZ_UNLIKELY(mozTryTempResult_.isErr())) { \
return mozTryTempResult_.propagateErr(); \
__extension__({ \
auto mozTryVarTempResult = ::mozilla::ToResult(expr); \
if (MOZ_UNLIKELY(mozTryVarTempResult.isErr())) { \
return mozTryVarTempResult.propagateErr(); \
} \
} while (0)
mozTryVarTempResult.unwrap(); \
})
/**
* MOZ_TRY_VAR(target, expr) is the C++ equivalent of Rust's `target =
* try!(expr);`. First, it evaluates expr, which must produce a Result value. On
* success, the result's success value is assigned to target. On error,
* immediately returns the error result. |target| must be an lvalue.
*
* This macro is obsolete and its usages should be replaced with `MOZ_TRY`.
*/
#define MOZ_TRY_VAR(target, expr) \
do { \
auto mozTryVarTempResult_ = (expr); \
if (MOZ_UNLIKELY(mozTryVarTempResult_.isErr())) { \
return mozTryVarTempResult_.propagateErr(); \
} \
(target) = mozTryVarTempResult_.unwrap(); \
} while (0)
#define MOZ_TRY_VAR(target, expr) (target) = MOZ_TRY(expr);
#endif // mozilla_Try_h

View File

@@ -152,6 +152,12 @@ static Result<int, Failed> Task3(bool pass1, bool pass2, int value) {
return x + y;
}
static Result<int, Failed> Task4(bool pass1, bool pass2, int value) {
auto x = MOZ_TRY(Task2(pass1, value));
auto y = MOZ_TRY(Task2(pass2, value));
return x + y;
}
static void BasicTests() {
MOZ_STATIC_AND_RELEASE_ASSERT(Task1(true).isOk());
MOZ_STATIC_AND_RELEASE_ASSERT(!Task1(true).isErr());
@@ -168,9 +174,9 @@ static void BasicTests() {
Task1UnusedZeroEnumErr(false).unwrapErr());
// MOZ_TRY works.
MOZ_STATIC_AND_RELEASE_ASSERT(Task2(true, 3).isOk());
MOZ_STATIC_AND_RELEASE_ASSERT(Task2(true, 3).unwrap() == 3);
MOZ_STATIC_AND_RELEASE_ASSERT(Task2(true, 3).unwrapOr(6) == 3);
MOZ_RELEASE_ASSERT(Task2(true, 3).isOk());
MOZ_RELEASE_ASSERT(Task2(true, 3).unwrap() == 3);
MOZ_RELEASE_ASSERT(Task2(true, 3).unwrapOr(6) == 3);
MOZ_RELEASE_ASSERT(Task2(false, 3).isErr());
MOZ_RELEASE_ASSERT(Task2(false, 3).unwrapOr(6) == 6);
@@ -178,9 +184,14 @@ static void BasicTests() {
MOZ_STATIC_AND_RELEASE_ASSERT(Task2UnusedZeroEnumErr(true, 3).unwrap() == 3);
MOZ_STATIC_AND_RELEASE_ASSERT(Task2UnusedZeroEnumErr(true, 3).unwrapOr(6) ==
3);
MOZ_STATIC_AND_RELEASE_ASSERT(Task2UnusedZeroEnumErr(false, 3).isErr());
MOZ_STATIC_AND_RELEASE_ASSERT(Task2UnusedZeroEnumErr(false, 3).unwrapOr(6) ==
6);
MOZ_RELEASE_ASSERT(Task2UnusedZeroEnumErr(false, 3).isErr());
MOZ_RELEASE_ASSERT(Task2UnusedZeroEnumErr(false, 3).unwrapOr(6) == 6);
MOZ_RELEASE_ASSERT(Task4(true, true, 3).isOk());
MOZ_RELEASE_ASSERT(Task4(true, true, 3).unwrap() == 6);
MOZ_RELEASE_ASSERT(Task4(true, false, 3).isErr());
MOZ_RELEASE_ASSERT(Task4(false, true, 3).isErr());
MOZ_RELEASE_ASSERT(Task4(false, true, 3).unwrapOr(6) == 6);
// MOZ_TRY_VAR works.
MOZ_RELEASE_ASSERT(Task3(true, true, 3).isOk());