1 个不稳定版本
0.1.0 | 2021年9月20日 |
---|
#6 在 #can-t
8KB
锚定
使事物锚定并禁止它们穿越 .await
点。
lib.rs
:
使事物锚定并禁止它们穿越 .await
点。
快速示例
本软件包中的两个基本元素是
- [unanchored] 属性宏,用于标记您希望应用 .await 点穿越检查的函数和方法。
- [Anchored] 包装结构,用于包装您想要锚定的内容。
它们通常像这样一起工作:首先,将 [Anchored] 包装在您想要远离 .await 的对象上,如 MutexGuard、&mut 引用或其他内容。然后,将 [unanchored] 属性添加到异步函数/方法中,以启用此编译时检查。
这就是全部。现在编译器会为您进行检查。
例如,以下代码无法编译,因为 bar 正在尝试穿越 .await 点。
#[unanchored]
async fn foo(){
let bar = Anchored::new(Bar {});
async_fn().await;
drop(bar);
}
限制 bar 的作用域后,一切正常。
#[unanchored]
async fn foo(){
{
let bar = Anchored::new(Bar {});
}
async_fn().await;
}
动机
某些类型在异步上下文中不是预期使用的,例如来自 std 的阻塞 Mutex、内部可变包装 RefCell 或 UnsafeCell。在 .await 点保持它们可能会导致意外问题,如数据竞争、死锁等。当在异步块中使用这些类型时,我们需要仔细检查以确保它们在同步上下文中受限制。换句话说,不要穿越 .await 点。
此软件包提供了一种在编译时强制执行此检查的方法。它比手动检查提供更多安全性,并且可以避免像 RefCell 那样在运行时进行的某些检查。
如何
此软件包非常简单。它引入了一个自动 trait Unanchored,并针对 [Anchored] 取消了它。类似于 std 中的 Unpin trait 和 Pin 包装器。
我们使用将异步块转换为生成器的机制。在转换到生成器之前,作用域中的所有变量都会被捕获到生成器的状态中。通过检查转换后的未来的类型,我们可以判断 [Anchored] 是否被捕获(穿越了 .await 点)。
限制
类型推理阶段只关注“作用域”,而不会考虑“所有权”。这意味着丢弃变量是正常且在运行时没有问题的,但它将导致检查失败。
#[unanchored]
async fn foo(){
let bar = Anchored::new(Bar {});
drop(bar);
async_fn().await;
}
它应该像上面的示例那样明确地封装在一个更小的作用域中。
相关工作
clippy 提供了两种 lint 选项 await_holding_lock
和 await_holding_refcell_ref
,可以检查某些特定的锁和引用类型。
依赖项
~1.5MB
~35K SLoC