Folly `safe_closure` 实战指南:编译期安全的参数绑定闭包
Follysafe_closure实战指南编译期安全的参数绑定闭包【免费下载链接】follyAn open-source C library developed and used at Facebook.项目地址: https://gitcode.com/GitHub_Trending/fol/follysafe_closure是 Meta 开源的 C 库 Folly 中位于 folly/lang/SafeClosure.h 的一个工具它创建一个按值存储参数、可多次调用的可调用对象其 API 语义类似 Python 的functools.partial但额外提供了面向safe_alias安全 API 的编译期安全保证。本文将结合仓库源码与测试系统讲解其基本用法、参数存储与绑定语义、调用限定符约束、与async_closure的取舍以及在folly/coro/safe异步代码中借助coro::capture收集结果的高阶用法。什么是safe_closure从源码注释folly/lang/SafeClosure.h看safe_closure()是对带值捕获的 lambda的重实现暴露一个与 Pythonfunctools.partial类似的 API。当你需要把一个可调用对象传入safe_alias感知的 API例如 folly/coro/safe 目录下的异步闭包设施时它比普通 lambda 更合适普通 lambda 可以持有任意捕获其中可能隐藏指向外部栈帧的引用源码中称为 lambda hole编译器无法判断其内存安全性safe_closure只接受安全的可调用对象与参数——函数指针、无捕获 lambda、空可调用类或满足strict_safe_alias_of_vT safe_alias::closure_min_arg_safety的类型它会度量其存储参数的safe_alias_of安全等级并据此标记返回的闭包从而让安全 API 在编译期捕获大量生命周期安全隐患传入不安全的函数或参数是编译错误——因为如果真的需要一个不安全的闭包直接用带捕获的 lambda 即可。基本用法safe_closure的用法非常直观第一个参数是bind::args{...}包裹的参数列表第二个参数是待绑定的函数/可调用对象。被绑定的参数存储在闭包内部调用时只需提供剩余未绑定的参数#include folly/lang/SafeClosure.h namespace bind folly::bind; using folly::safe_closure; int add(int a, int b, int c) { return a b c; } // Bind the first two arguments of add auto fn safe_closure(bind::args{10, 20}, add); // Sum the already-captured 10 20 with a newly-provided 5. // Return 35 add(10, 20, 5) int result fn(5);推荐在.cpp文件或项目命名空间内使用namespace bind folly::bind;简写见 folly/lang/bind/Bind.md避免每次书写冗长的folly::bind::前缀。从实现上看folly/lang/SafeClosure.hsafe_closureFn, tag_tvtag_tArgBIs..., ArgsTup内部持有Fn fn_与一个按值存储的参数元组ArgsTup argsTup_构造时通过推导指引把bind::args展开为 绑定信息标签bind_info_t与存储元组两部分。vtag_t记录了每个参数的bind::动词信息用于决定调用时以何种方式把存储值传给函数。参数存储始终按值存储safe_closure中的参数总是按值存储在闭包内你通过标准 C 语义控制存储方式std::string s hello; auto fn safe_closure( bind::args{ s, // copy s into closure std::move(s), // move s into closure std::string{world}}, // construct and move prvalue // These all deduce to const std::string -- see the next section [](auto a, auto b, auto c) { return a b c; });注意存储的是值因此闭包可安全地脱离原始变量独立存在这也是其内存安全性的基石。源码中deduce_safe_closure_paramsfolly/lang/SafeClosure.h通过bind_to_storage_policy计算出每个参数的存储类型并特意始终以可变值方式存储——因为若存储引用闭包就会变成不安全的。对于不可移动、不可拷贝的类型或者希望避免额外的移动/拷贝可使用bind::in_place进行真正意义上的原地构造auto fn safe_closure( bind::args{bind::in_placestd::string, hello}, [](auto s) { return s.size(); });bind::in_placeT(args...)内部通过一个隐式可转换的 maker 对象见 folly/lang/bind/Bind.h 中的in_place_args_maker直接在存储元组内构造T从而支持不可移动类型。如果构造逻辑更复杂还有bind::in_place_with(make_fn, args...)它把工厂函数与参数一并传入folly/lang/bind/Bind.h。需要警惕的是bind::in_place返回的对象内部保存了指向args的引用只应在构造它的那条语句内使用clang::lifetimebound标注会帮助捕获悬垂引用。若需要跨语句返回一个持有值的 binding应改用bind::in_place_with([n]() { return Foo{n}; })这种把值捕获进工厂 lambda 的写法详见 folly/lang/bind/Bind.md。参数绑定默认按 const 传递可用bind::动词控制safe_closure默认以const方式把存储的参数绑定给函数。因此即使示例 lambda 参数写作auto实际推导出的也是const T你也可以显式写const auto。但是如果把参数声明为std::string不配合bind::mut就会编译失败——这正是设计意图显式地暴露可变引用避免隐式修改带来的 bug。使用bind::动词精确控制存储参数如何传递给函数std::string s test; auto fn safe_closure( bind::args{ s, // pass by const reference (default) bind::mut{s}, // pass by mutable reference bind::copy{s}, // pass by value (decay-copy) bind::move{std::move(s)}}, // pass by rvalue reference [](/*const*/ auto a, auto b, auto c, auto d) { // a: const std::string // b: std::string // c: std::string // d: std::string });这些动词的底层语义记录在bind_info_t的category_t与constness_t两个枚举中folly/lang/bind/Bind.hcategory_t决定按引用、按值、decay-copy 还是右值传递constness_t决定目标的const性。实际传递逻辑由 folly/lang/bind/AsArgument.h 中的bind_as_argument实现category_t::ref或unset默认按引用传递。constness_t::constant/unset时类似std::as_const但支持右值引用constness_t::mut时按可变引用传递且会static_assert阻止在const闭包上使用bind::mut因为存储值在const调用中是不可变的category_t::copy用folly::copy做 decay-copy 后按值传递并断言存储来自左值引用从右值存储拷贝你是不是想要bind::movecategory_t::move按右值引用传递且断言存储本身是右值要使用bind::move请以右值方式调用闭包。测试 folly/lang/test/SafeClosureTest.cppLrefInvoke完整印证了这些推导std::move(a)存储为std::unique_ptrint后以const std::unique_ptrint传入bind::mut{b}使b以unsigned int传入并删除了operator() constbind::copy{cFn()}使c以int值传入。RrefInvokefolly/lang/test/SafeClosureTest.cpp则验证bind::move的调用路径其内层函数以auto接收并同样触发static_assert阻止非调用。常用bind::动词速查动词存储方式传给函数的方式说明裸值默认按值const T默认行为最安全bind::mut{v}按值T需要可变引用时使用会禁用const调用bind::constant{v}按值强制 const 存储const T相当于把非 const 对象移入 const 存储bind::copy{v}按值Tdecay-copy传递副本会禁用调用bind::move{std::move(v)}按值T移动传递只允许调用bind::in_placeT(args...)原地构造由上述规则决定支持不可移动类型调用限定符、const与闭包可以以不同的限定符被调用auto fn safe_closure(bind::args{42}, [](int x) { return x * 2; }); fn(0); // qualifier - can call multiple times std::as_const(fn)(0); // const qualifier - read-only access std::move(fn)(0); // qualifier - single-use, destructive对应实现是 folly/lang/SafeClosure.h 中的三个operator()重载与const可反复调用调用会移动存储参数与函数本身属于一次性、破坏性操作。const重载中函数以std::as_const(fn_)调用重载中存储参数与函数均被移动。重要约束使用bind::move与bind::copy会限制可用的调用限定符bind::copy禁用调用从右值存储做 decay-copy 没有意义bind::move禁用和const调用只有可用因为移动是破坏性的。bind::mut同理会禁用const调用——测试注释中明确指出这是刻意为之在const闭包上使用bind::mut极可能是 bug回忆 lambda 拷贝捕获默认是const的。这些约束都以static_assert形式在 folly/lang/bind/AsArgument.h 中落地错误信息会直接给出修复提示。safe_closure与async_closure的对比safe_closure与async_closure同属folly/lang绑定体系与folly/coro/safe异步体系互为补充safe_closureasync_closure返回一个可调用对象返回一个可等待对象awaitable可以反复调用仅一次性使用存储常规类型提供bind::capture以支持异步 RAII同步执行异步协程简单说需要同步、可复用的参数绑定用safe_closure需要异步执行、需要 RAII 式资源管理的场景用async_closure参见 folly/coro/safe/AsyncClosure.h。安全性与safe_alias与coro::capture的协作safe_closure只接受SafeAlias.h安全等级足够的可调用对象和参数不安全输入会直接导致编译错误。返回闭包的安全等级由函数与存储参数中较低者决定。这一点在实现中由两个static_assert与folly_private_safe_alias_t保证folly/lang/SafeClosure.hstatic_assert(strict_safe_alias_of_vFn safe_alias::closure_min_arg_safety); static_assert(lenient_safe_alias_of_vArgsTup safe_alias::closure_min_arg_safety);函数使用strict_safe_alias_of_v严格度量——因为普通 lambda 捕获是内存安全的重大风险点源码称其为 lambda hole存储参数使用lenient_safe_alias_of_v宽松度量——注释明确解释了原因若采用递归式严格度量任何一个未标注安全等级的参数都会让整个闭包变成严格不安全从而严重损害可用性闭包整体安全等级取min(strict(Fn), lenient(ArgsTup))。safe_alias体系的背景见 folly/lang/SafeAlias.h它是对类型是否内存安全的启发式度量核心只有两个等级——unsafe如裸指针、引用、std::reference_wrapper与maybe_value如int、std::pairint,char、std::unique_ptrFoo、函数指针。需要强调的是由于 C 缺乏完整反射该启发式可被绕过例如结构中隐藏的不安全成员其目标更谦逊让不安全的别名在代码评审中更可见并鼓励默认采用值语义。如需显式标注类型安全等级可以在类内提供folly_private_safe_alias_t成员别名或特化folly::safe_alias_of。基本的安全性示例// ✓ Safe: function pointer safe arguments auto safe_fn safe_closure(bind::args{42}, some_function); // ✗ Compile error: lambda with captures is unsafe int y 5; auto unsafe_fn safe_closure(bind::args{37}, { return x y; });真实异步场景配合safe_async_scope收集结果你可以向safe_closure传入coro::capture来引用由async_closure或async_object存储的数据。得益于编译期检查这不会引入生命周期安全风险。safe_closure内部通过safe_closure_arg_storage_helper的特化folly/lang/SafeClosure.h把captureV类型的存储转为captureV——该特化由 folly/coro/safe/Captures.h 提供因此SafeClosure.h无需直接依赖 coro 模块。一个典型用例是通过safe_async_scope收集计算结果namespace bind folly::bind; namespace coro folly::coro; namespace collect folly::coro::collect; value_taskOut computeAnswerForIndex(size_t i) { /*...*/ } size_t n 10; std::vectorOut output(n, 0); co_await coro::async_now_closure( bind::args{n, bind::capture_mut_ref(output), coro::safe_async_scope()}, [](size_t n, auto out, auto scope) - coro::closure_task { for (size_t i 0; i n; i) { coro::spawn( collect::redirect( computeAnswerForIndex(i), collect::log_and_ignore_non_values collect::fn_result_sink{folly::safe_closure( bind::args{out, i}, [](auto out2, size_t i2, value_only_resultint r) { (*out2)[i2] std::move(r).value_only(); })}), scope); } co_return; });从高层视角看这段代码把capturevectorOut一路传递到最内层的safe_closure由它最终写入计算值。这是对旧版 folly/coro/Collect.h 中典型算法的重写且有以下显著差异完全灵活可以自由更换输出容器、任务生成模式、本地可用值与引用、任务的取消或执行器策略而无需发明新的 coro 算法生命周期简化借助collect::机制异步作用域只需管理void、noexcept 可等待的完成体避免了旧版coro::AsyncScope中有时只记日志、有时直接崩溃的补丁式写法编译期安全整个计算正确处理safe_alias标注因此你可以在 scope 派生的任务内安全地持有coro::capture引用而不冒生命周期 bug 风险一旦误取不安全引用编译立即失败异常安全async_now_closure提供异步 RAII整个流程异常安全且异常时是否取消未完成的 scope 任务在 API 层就能简单自然地表达。safe_closure的价值在于让设置作用域内计算结果的落点甚至是对结果的变换变得极其简单。collect::map_result是与fn_result_sink对应的组件——同样接收可调用对象但用在redirect链的中间位置。未来工作仓库文档 folly/lang/SafeClosure.md 还列出两个规划方向添加 linter检查safe_closure中bind::限定符与可调用对象签名是否匹配。例如把参数按引用绑定到一个声明为auto的参数很可能是性能 bug——会引入不必要的拷贝而使用bind::copy时几乎不可能是想绑定到或const参数。推广must_use_immediately_v普通带引用捕获的 lambda 是不安全但可移动的。未来可能把 folly/coro/AwaitImmediately.h仓库中对应头文件为folly/lang/MustUseImmediately.h泛化为must_use_immediately_v并定义一个不安全但不可移动的safe_now_closure。虽然单独使用价值有限但可以简化泛型代码。参考阅读本文主体文档folly/lang/SafeClosure.md实现源码folly/lang/SafeClosure.h绑定体系文档folly/lang/bind/Bind.md实现folly/lang/bind/Bind.h、folly/lang/bind/AsArgument.h安全度量体系folly/lang/SafeAlias.h测试用例folly/lang/test/SafeClosureTest.cpp、folly/lang/test/SafeAliasTest.cpp异步配套folly/coro/safe含AsyncClosure.h、Captures.h、SafeTask.h等【免费下载链接】follyAn open-source C library developed and used at Facebook.项目地址: https://gitcode.com/GitHub_Trending/fol/folly创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考