如何用 folly result<T> 替代异常返回并用 or_unwind 传播错误
发布时间:2026/9/13 18:18:08 锦皓数字建站

如何用 folly result 替代异常返回并用 or_unwind 传播错误【免费下载链接】follyAn open-source C library developed and used at Facebook.项目地址: https://gitcode.com/GitHub_Trending/fol/folly在 C 服务代码里如果你希望某些关键路径不再抛异常、错误必须被显式检查和处理folly 的folly/result提供了resultT函数不再throw而是返回resultT其中要么装着值要么装着错误std::exception_ptr或 stopped取消状态。配合协程语法co_await or_unwind(...)未处理的错误会沿调用链向上传播类似 Rust 的?运算符。本文的任务是把一段抛异常的同步函数改写成resultT协程用or_unwind传播错误并在调用点用get_exception/get_rich_error_code完成处理与验证。前提C20 与协程支持result及相关宏由FOLLY_HAS_RESULT门控。在 Portability.h 中可以看到判定条件必须同时满足 C20FOLLY_CPLUSPLUS 202002L且编译启用了协程支持FOLLY_HAS_COROUTINES否则FOLLY_HAS_RESULT为 0result的协程能力包括or_unwind不可用。因此你的构建需要 C20 标准和支持协程的编译器。用到本场景需要包含的头文件#include folly/result/result.h // resultT, error_or_stopped, get_exception #include folly/result/coro.h // or_unwind, or_unwind_owning // 需要错误上下文时再加 #include folly/result/or_unwind_epitaph.hAPI 细节以头文件内的 docblock 为准完整用法见 result.md。第一步函数返回 result 错误不再 throwresultT返回的函数体写成协程即可。错误分支不throw而是返回error_or_stopped{异常对象}// 这个 result coro 就像一个返回 result 的普通同步函数 // 但它等价于函数体被 try {} catch(...) { // return error_or_stopped::from_current_exception(); } 包围 resultsize_t countGrapefruitSeeds() { // 若 getFruitBox() 返回 error 或 stopped // co_await or_unwind 会立即把该状态向上传播 auto box co_await or_unwind(getFruitBox()); auto boxRes box.findFruit(FruitTypes::GRAPEFRUIT); if (auto ex get_exceptionRottenFruit(boxRes)) { logDiscardedGrapefruit(ex); // ex 用起来像 const RottenFruit* return 0; } const auto grapefruit co_await or_unwind(boxRes); size_t numSeeds 0; // fetchSegments() 返回的不是 result它抛出的异常 // 会被捕获并打进本函数的返回值 auto segments grapefruit.fetchSegments(); for (auto segment : segments) { numSeeds (co_await or_unwind(segment.seeds())).size(); } co_return numSeeds; }这段代码来自 result.md 的 Use-case 1 示例其中getFruitBox、findFruit、RottenFruit等为示意符号替换为你自己的函数与异常类型即可。要点result协程而不是普通函数是异常边界函数体内任何未被捕获的异常都会被自动捕获进返回值调用方可以假定该函数不会向外抛异常除std::bad_alloc及参数拷贝/移动构造之外。resultT是[[nodiscard]]单写一句resultFoo()不处理会编译失败你必须显式co_await or_unwind(resultFoo())从机制上杜绝忘记检查返回值。不要给result协程加noexcept——文档明确说明这会导致参数构造抛异常或std::bad_alloc时std::terminate。第二步用 co_await or_unwind 向上传播or_unwind定义见 coro.h支持的类型有resultT、value_only_resultT和error_or_stopped。行为规则参数是值取出值返回是 error 或 stopped立即短路把该状态传给当前协程的 awaiter向上传播。取值方式影响拷贝co_await or_unwind(resFn())对右值返回T错误路径上exception_ptr以 move 传播约 1ns对左值co_await or_unwind(res)返回T错误路径上exception_ptr是拷贝约 7ns。错误路径热时用std::move(res)冷路径优先可读性。不要用auto ref co_await or_unwind(resFn())这是文档标注的已知悬垂问题见 result.md 的 Known issue 一节引用绑定到临时结果后在语句结束即失效。安全写法是auto val co_await or_unwind(resFn());按值存储、co_await or_unwind_owning(resFn())或先存结果再std::move传递。处理特定错误时用folly::get_exception替代try-catch多类错误用else if链式判断result handlesErrors() { auto r propagatesErrors(); if (auto* ex get_exceptionMyErr(r)) { // 处理 ex用起来像 const MyErr* } else { auto v co_await or_unwind(std::move(r)); // 未处理的 error 或 stopped 继续传播 } }配合rich_error体系见 rich_error.mdget_exceptionErr(res)返回一个指针-like 对象直接支持fmt和operator会打印错误码、消息、源码位置和传播上下文epitaph 栈。注意查询时写具体的Err类型不要写rich_errorErrif (auto err get_exceptionFruitError(res)) { // 注意不是 FruitError* // 打印 code、message、source location 与传播上下文 LOG(ERROR) Failed to peel fruit: err; }如果只需要错误码用get_rich_error_codeCode(container)返回std::optionalCode查不到 code不等于没有错误错误可能用了别的 code 或不支持 code 协议。可选用 epitaph 给错误附加传播上下文错误沿调用链传播时会丢失在哪一层出的问题。epitaph包装器可以给 error/stopped 状态附加一条消息和源码位置构建轻量错误栈or_unwind_epitaph是or_unwind(epitaph(...))的语法糖见 or_unwind_epitaph.h值路径上跳过 epitaph 零开销错误路径才附加// 来自 result.md co_return co_await or_unwind_epitaph( resultFn(), context {}, arg); // 等价的手写形式来自 rich_error.md co_await or_unwind(epitaph(resultFn(), in {} due to {}, place, reason));epitaph 是透明的get_exceptionEx()访问到的是底层错误而非包装器。热路径可以不加 epitaph——文档给出的当前开销约为 60ns属于可选项按需启用。验证跑仓库内的 demo 或按同样的模式检查仓库提供了一个可直接阅读的完整示例 demo/basketball.cpp它演示了错误产生make_coded_rich_error、带 epitaph 的传播or_unwind(epitaph(...))以及调用点的完整处理循环int main() { Player inbounder, pointGuard, shootingGuard; pointGuard.passingLaneBlocked true; while (true) { auto points runFastBreak(inbounder, pointGuard, shootingGuard); if (points.has_value()) { return points.value_or_throw(); } if (auto ex get_exceptionstd::exception(points)) { fmt::print(Debug log: {}\n, ex); } if (std::errc::resource_unavailable_try_again get_rich_error_codestd::errc(points)) { pointGuard.passingLaneBlocked false; fmt::print(retrying after clearing the lane\n); continue; } return 0; // Score no points (currently unreachable) } }main()里的has_value()/get_exception/get_rich_error_code三连就是验证错误处理的检查方式先区分值与错误再按类型查具体异常最后按 code 做分支demo 中std::errc只是示意文档特意注明真实代码不要拿std::errc当业务错误码。README.md 给出了该 demo 在错误路径上的文档示例输出不是每次运行都必须一致的固定日志源码位置和行号会随版本变化passing lane blocked - std::errc11 (Resource temporarily unavailable) result/demo/basketball.cpp:39 [via] fast break collapsed result/demo/basketball.cpp:47其中[via]后面就是 epitaph 添加的上下文。你自己的代码若也能打出错误消息 来源位置 [via] 传播上下文说明 rich error 与 epitaph 链路工作正常。限制与非协程替代路径以下约束来自文档直接影响你能怎么写调试构建下的硬性校验error_or_stopped若装入空std::exception_ptr或OperationCancelled在 debug 构建中会std::terminate见 result.h 顶部 docblock。取消应使用stopped_result/has_stopped()而不是抛OperationCancelled。value_or_throw()只用于边缘场景在result协程里它就是co_await or_unwind(res)的低效写法文档明确不推荐若写非协程的热路径函数先显式if (res.has_value())再用它让throw 分支不可能发生。非协程替代路径老编译器等确实不能用协程时可以写普通函数返回resultT调用方无感知差异但必须遵守基本不抛契约。result.md 给出的写法是用result_catch_all包住函数体内部错误用return rh.error_or_stopped();手动传播#include folly/result/result.h resultint plantSeeds(int n) { return result_catch_all([]() - resultint { if (n 0) { return error_or_stopped{std::logic_error{cannot plant 0 seeds}}; } int seedsLeft n; for (int i 0; i n; i) { auto rh digHole(i); if (auto ex get_exceptionHitBigRock(rh)) { continue; // 跳过这颗种子 } else if (!rh.has_value()) { return rh.error_or_stopped(); // 未处理的 error 或 stopped } rh.value_or_throw().plantSeed(i); --seedsLeft; } return seedsLeft; }); }性能预期文档基准供决策参考错误传播是 O(1ns) 级而throw超过 1µsor_unwind错误路径为 3-30ns值路径每个函数调用多约 1 个分支的开销。富错误整体比整数错误码慢约 10 倍、比抛异常快 10-200 倍高频错误条件可考虑immortal_rich_errorconstexpr 单例构造/销毁 5ns。需要与异步folly::coro任务互操作时同一套语法仍然成立res co_await value_or_error_or_stopped(task())拿到result后照常co_await or_unwind(res)纯向上传播异常则继续用co_await co_nothrow(childTask())。与旧Try代码的互操作通过result_to_try()/try_to_result()完成选型依据见 design_notes.md。【免费下载链接】follyAn open-source C library developed and used at Facebook.项目地址: https://gitcode.com/GitHub_Trending/fol/folly创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。