Skip to content

Commit b149a83

Browse files
snarkmasterfacebook-github-bot
authored andcommitted
non_value_result API should not be defined in terms of exception_wrapper
Summary: The prior iteration of `non_value_result` and `result<T>` copiously leaked the fact that we use `exception_wrapper` as the implementation of `non_value_result`. This turned out to be very undesirable for my future RTTI-avoidance work. This diff improves usability, since `non_value_result{YourError{}}` is clearer and shorter than `make_exception_wrapper<YourError>()`. At the same time, it practically eliminates usage of bare `exception_wrapper` in the `result` API. Only the explicitly-for-compatibility shims of `non_value_result::from_exception_wrapper`, and `non_value_result::to_exception_wrapper()` remain. This makes it easy to drop in support for immortal exception pointers down the line. Reviewed By: ispeters Differential Revision: D73410513 fbshipit-source-id: db619bd115230247a4d45fc6f6b9130a1a108278
1 parent 0305b97 commit b149a83

File tree

8 files changed

+188
-210
lines changed

8 files changed

+188
-210
lines changed

third-party/folly/src/folly/coro/test/ReadyTest.cpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ namespace folly::coro {
2525

2626
CO_TEST(ReadyTest, co_ready_of_error_result) {
2727
auto errorTaskFn = []() -> NowTask<> {
28-
result<> res = make_exception_wrapper<std::runtime_error>("foo");
28+
result<> res = non_value_result{std::runtime_error{"foo"}};
2929
try {
30-
co_await co_ready(std::move(res));
30+
co_await co_ready{std::move(res)}; // will not throw
3131
} catch (...) {
32-
} // propagates exception WITHOUT rethrowing
32+
}
3333
LOG(FATAL) << "not reached";
3434
};
3535
auto res = co_await co_await_result(errorTaskFn());
@@ -39,14 +39,14 @@ CO_TEST(ReadyTest, co_ready_of_error_result) {
3939
CO_TEST(ReadyTest, co_ready_of_value_result) {
4040
auto valueTaskFn = []() -> NowTask<int> {
4141
result res = 1300;
42-
co_return 37 + co_await co_ready(std::move(res));
42+
co_return 37 + co_await co_ready{std::move(res)};
4343
};
4444
EXPECT_EQ(1337, co_await valueTaskFn());
4545

4646
// Use a move-only value to check we don't have extraneous copies
4747
auto moveValueTaskFn = []() -> NowTask<std::unique_ptr<int>> {
4848
result res = std::make_unique<int>(1300);
49-
auto np = co_await co_ready(std::move(res));
49+
auto np = co_await co_ready{std::move(res)};
5050
*np += 37;
5151
co_return std::move(np);
5252
};
@@ -56,7 +56,7 @@ CO_TEST(ReadyTest, co_ready_of_value_result) {
5656
CO_TEST(ReadyTest, co_ready_of_void_result) {
5757
auto taskFn = [&]() -> NowTask<int> {
5858
result<> res;
59-
co_await co_ready(std::move(res));
59+
co_await co_ready{std::move(res)};
6060
co_return 42;
6161
};
6262
EXPECT_EQ(42, co_await taskFn());
@@ -65,7 +65,7 @@ CO_TEST(ReadyTest, co_ready_of_void_result) {
6565
CO_TEST(ReadyTest, co_ready_stopped) {
6666
auto taskFn = [&]() -> NowTask<int> {
6767
result<> res = stopped_result;
68-
co_await co_ready(std::move(res));
68+
co_await co_ready{std::move(res)};
6969
co_return 42;
7070
};
7171
EXPECT_TRUE((co_await co_await_result(taskFn())).has_stopped());

third-party/folly/src/folly/result/README.md

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ more usable:
2828
[`folly::Expected`](
2929
https://github.com/facebook/folly/commit/0e8c7e1c97e27d96fddbdf552bc99faa22066d00).
3030

31-
- `result` uses `exception_wrapper`, `folly`'s enhanced `std::exception_ptr`
31+
- `result` uses `std::exception_ptr` (with some folly-specific enhancements)
3232
to efficiently transport all exception types, which avoids the user-facing
3333
complexity of distinguishing `outcome::result` (`error_code` only) and
3434
`outcome::outcome` (code OR exception).
@@ -160,17 +160,17 @@ In bullets, `result<T>`:
160160
161161
- Contains one of:
162162
* `T` -- which can be a value or reference, or
163-
* `non_value_result` -- which either `has_stopped()`, or `has_error()`.
164-
Error storage is `exception_wrapper`, an enhanced wrapper for
165-
`std::exception_ptr`.
163+
* `non_value_result` -- which either `has_stopped()`, or stores an error as
164+
`std::exception_ptr`, with folly-specific optimizations. Access the
165+
latter via `folly::get_exception<Ex>(res)`.
166166
167167
*NB*: Right now, `result` is "almost never empty, like `folly::Expected`",
168168
but when C++23 is widely available, it will be truly never empty.
169169
170170
- Provides constructors optimized for usability. For example, unlike
171171
`folly::Try`, `result` is implicitly constructible from values & errors.
172172
173-
*NB*: Storing an empty `exception_wrapper` is a contract violation. It is
173+
*NB*: Storing an empty `std::exception_ptr` is a contract violation. It is
174174
fatal in debug builds, but "works" in opt unless you hit a later assertion,
175175
like the `std::terminate` in `exception_wrapper::throw_exception`.
176176
@@ -196,15 +196,15 @@ In bullets, `result<T>`:
196196
- Supports migration from `Try` via `try_to_result` and `result_to_try`.
197197
198198
- Uses automatic storage duration for `T`. Allocation caveats:
199-
* `exception_wrapper` uses the heap via `std::exception_ptr`.
199+
* `std::exception_ptr` stores the exception on the heap.
200200
* While `result` coroutines are set up to be HALO-friendly, a compiler is
201201
not *obligated* to allocate `result` coroutines on the stack. Profile
202202
first, then read "how to avoid coro frame allocations" below.
203203
204204
What to know about exceptions & `result`:
205205
206206
- `result` coroutines (but **not** functions) are exception boundaries.
207-
Any uncaught exception is returned as `res.non_value().error()`.
207+
Any uncaught exception is captured in `res.non_value()` & returned.
208208
209209
- The `result` API avoids throwing, aside from:
210210
* `value_or_throw()`, which you should avoid in favor of `co_await`,
@@ -277,8 +277,8 @@ Both are implicitly movable contexts, so the `std::move` is just visual
277277
noise, and can actually prevent NVRO for `return` (there's a linter against it).
278278
279279
You can directly return any of these types: `result<V>`, `V`,
280-
convertible-to-`V`, convertible-to-`result<V>`, `exception_wrapper`,
281-
`non_value_result`, or `stopped_result`. None will incur unnecessary copies.
280+
convertible-to-`V`, convertible-to-`result<V>`, `non_value_result`, or
281+
`stopped_result`. None will incur unnecessary copies.
282282
283283
## How to...
284284
@@ -345,14 +345,14 @@ Most of the time, you will await a prvalue result, i.e. `co_await resultFunc()`.
345345
This moves the underlying value, or exposes the returned reference.
346346

347347
However, if you have `result<V> r`, then `co_await r` will not compile -- that
348-
would have to copy `V` or `exception_wrapper`, and the `result` API tries to
348+
would have to copy `V` or `std::exception_ptr`, and the `result` API tries to
349349
make copies explicit.
350350

351351
Instead, you have to choose from:
352352
- By-value: `co_await std::move(m)`: Returns a moved-out `V`. Error
353353
propagation is fast.
354354
- By-reference: `co_await std::cref(m)` / `std::ref(m)`: Returns `const V&` /
355-
`V&`. Propagates `exception_wrapper` by copying (~25ns).
355+
`V&`. Propagates `std::exception_ptr` by copying (~25ns).
356356

357357
For some `V`, `co_await`ing by-reference can speed up value access, at the
358358
expense of the error path.
@@ -400,8 +400,7 @@ difference, **as long as you follow the "mostly non-throwing" contract** of
400400
result<int> plantSeeds(int n) {
401401
return result_catch_all([&]() -> result<int> {
402402
if (n < 0) {
403-
return make_exception_wrapper<std::logic_error>(
404-
"cannot plant < 0 seeds");
403+
return non_value_result{std::logic_error{"cannot plant < 0 seeds"}};
405404
}
406405
int seedsLeft = n;
407406
for (int i = 0; i < n; ++i) {}

third-party/folly/src/folly/result/gtest_helpers.h

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,16 @@
2121

2222
namespace folly {
2323

24-
#define RESULT_CO_UNWRAP_BODY(body) \
25-
{ \
26-
auto ret = body(); \
27-
if (!ret.has_value()) { \
28-
if (ret.non_value().has_error()) { \
29-
FAIL() << ret.non_value().error(); \
30-
} else { \
31-
FAIL() << "RESULT_CO_TEST got cancellation"; \
32-
} \
33-
} \
24+
#define RESULT_CO_UNWRAP_BODY(body) \
25+
{ \
26+
auto ret = body(); \
27+
if (!ret.has_value()) { \
28+
if (ret.non_value().has_stopped()) { \
29+
FAIL() << "RESULT_CO_TEST got cancellation"; \
30+
} else { \
31+
FAIL() << std::move(ret).non_value().to_exception_wrapper(); \
32+
} \
33+
} \
3434
}
3535

3636
/*

third-party/folly/src/folly/result/result.cpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,28 +24,28 @@
2424
namespace folly::detail {
2525

2626
const non_value_result& dfatal_get_empty_result_error() {
27-
static const folly::Indestructible<non_value_result> r{
28-
make_exception_wrapper<empty_result_error>()};
27+
static const folly::Indestructible<non_value_result> r{empty_result_error{}};
2928
LOG(DFATAL) << "`folly::result` had an empty underlying `folly::Expected`";
3029
return *r;
3130
}
3231

3332
const non_value_result& dfatal_get_bad_result_access_error() {
3433
static const folly::Indestructible<non_value_result> r{
35-
make_exception_wrapper<bad_result_access_error>()};
36-
LOG(DFATAL) << "Used `error()` accessor for `folly::result` in value state";
34+
bad_result_access_error{}};
35+
LOG(DFATAL)
36+
<< "Used `non_value()` accessor for `folly::result` in value state";
3737
return *r;
3838
}
3939

4040
void fatal_if_exception_wrapper_invalid(const exception_wrapper& ew) {
4141
if (!ew.has_exception_ptr()) {
42-
LOG(FATAL) << "`result` may not contain an empty `exception_wrapper`";
42+
LOG(FATAL) << "`result` may not contain an empty `std::exception_ptr`";
4343
}
4444
if (folly::get_exception<folly::OperationCancelled>(ew)) {
4545
LOG(FATAL)
46-
<< "Do not put `OperationCancelled` in `result`, or get it via "
47-
<< "`error()`. Instead, store `stopped_result{}`, and check "
48-
<< "`has_error()` (or `!has_stopped()`) before calling `error()`.";
46+
<< "Do not store `OperationCancelled` in `result`. If you got this "
47+
<< "error while extracting an `exception_wrapper`, `exception_ptr`, "
48+
<< "or similar, you must check `has_stopped()` before doing that!";
4949
}
5050
}
5151

0 commit comments

Comments
 (0)