2411rust,异步函数
原文
Rust
异步工作组很高兴
地宣布
,在实现在特征
中使用异步 fn
的目标
方面取得了重大进度
.将在下周发布稳定的Rust1.75
版,会包括特征
中支持impl Trait
注解和async fn
.
稳定化
自从RFC#1522
在Rust1.26
中稳定下来以来,Rust
就允许用户按函数的返回类型
(一般叫"RPIT"
)编写impl Trait
.
即该函数
返回"某种实现特征
的类型
".这一般来返回闭包,迭代器
和其他复杂或无法显式编写
的类型.
//给定一个玩家列表,返回在他们的`名字`上的`一个迭代器`.
fn player_names(
players: &[Player]
) -> impl Iterator<Item = &String> {
players
.iter()
.map(|p| &p.name)
}
从Rust1.75
开始,你可在特征
(RPITIT
)定义和trait impl
中使用返回位置
的impl Trait
.如,你可用它来编写一个返回迭代器
的特征
方法:
trait Container {
fn items(&self) -> impl Iterator<Item = Widget>;
}
impl Container for MyContainer {
fn items(&self) -> impl Iterator<Item = Widget> {
self.items.iter().cloned()
}
}
则这一切与异步函数
有什么关系呢?嗯,异步函数
只是返回->impl Future
的函数的"语法糖
".因为在特征
中,现在允许这些,还允许你编写使用async fn
的特征
.
trait HttpService {
async fn fetch(&self, url: Url) -> HtmlBody;
//^^^^^^^^变为:
// fn fetch(&self, url: Url) -> impl Future<Output = HtmlBody>;
}
差距在哪?
公开特征中的->impl
特征
仍不建议在公开特征
和API
中普遍使用->impl Trait
,因为用户无法对返回类型
加限制.如,无法对容器
特征通用的编写
此函数:
fn print_in_reverse(container: impl Container) {
for item in container.items().rev() {
//错误:^^^对`'impl Iterator<Item=Widget>'`未实现`'DoubleEndedIterator'`特征
eprintln!("{item}");
}
}
尽管某些实现
可能会返回实现DoubleEndedIterator
的迭代器
,但在不定义
另一个特征
时,泛型代码
无法利用它
.
未来,打算
为此添加一个解决方法
.当前,->impl Trait
最适合内部特征
,或当你确信
用户不需要额外
约束时.否则,应该考虑使用关联类型
.
公开特征中的异步函数
因为async fn
解糖为->impl Future
,因此有同样限制
.事实上,如果今天在公开特征
中使用空的异步fn
,则会看到警告.
警告:不建议在公开特征
中使用"async fn"
,因为无法指定自动
特征约束.
异步
用户特别感兴趣
的是,在返回的未来
上的发送
约束.因为用户以后无法添加约束
,因此错误消息
说明你要选择
:是否想你的特征
与多线程
,窃取工作
程序一起使用
?
好的是,现在有个允许在公开特征
中使用异步fn
的方法!建议使用trait_variant::make
过程宏来让你的用户选择
.
该过程
宏是由rustlang
组织发布的traitvariant
包的一部分
.在项目中加上cargo add trait-variant
.使用:
#[trait_variant::make(HttpService: Send)]
pub trait LocalHttpService {
async fn fetch(&self, url: Url) -> HtmlBody;
}
这创建两个版本
的特征
:用LocalHttpService
针对单线程
执行器,HttpService
针对多线程工作
窃取执行器
.因为后者更常用
,因此此例中名字更短
.
它有额外的发送
约束:
pub trait HttpService: Send {
fn fetch(
&self,
url: Url,
) -> impl Future<Output = HtmlBody> + Send;
}
该宏适合异步
,因为impl Future
很少需要发送
以外的额外约束
,因此可成功为用户
准备好.
动态分发
使用->impl Trait
和asyncfn
的特征
不是对象安全
的,即不支持动态分发
.准备在未来推出的trait-variant
包版本中启用动态分发
.
未来如何改进
未来,希望允许用户添加自己的约束
到impl Trait
返回类型,这样更普遍更有用
.它还支持异步fn
的更高级用法
.语法
可能如下:
trait HttpService = LocalHttpService<fetch(): Send> + Send;
因为这些别名
不需要特征作者
的支持,因此,因此不需要异步特征
的发送
变量.但是,这些变量
仍会方便用户,因此期望大多数包
继续提供它们.
常见问题解答
是否可在特征
中使用->impl Trait
对私有特征
,可自由
使用->impl Trait
.对公开特征
,最好暂时避免使用它们
,除非可预见到用户可能需要的所有约束
(此时,你可用#[trait_variant::make]
,与异步
一样).
期望取消来会此限制.
是否仍应使用#[async_trait]
宏
你可能要继续使用异步特征
的原因
有几个:
1,想支持低于1.75
的Rust
版本.
2,你需要动态分发.
如上,希望在未来版本
中启用动态分发
.
可在特征
中使用async fn
吗?有哪些限制
假设,你不用#[async_trait]
,则完全可以在特征
中使用普通
的异步 fn
.如果想支持多线程运行时
,记住使用#[trait_variant::make]
.
最大限制
是类型
必须总是决定它
实现了特征
的发送
版本还是非发送
版本.它不能在其泛型之一上
有条件地实现发送
版本.
这可在中间件模式
中出现,如,如果T:HttpService
,则为HttpService
的RequestLimitingService<T>
.
为什么我需要#[trait_variant::make]
和Send
约束
简单情况时,发现你的特征
似乎与多线程程序
配合得很好.但是,有些模式
下不管用
.考虑以下
:
fn spawn_task(service: impl HttpService + 'static) {
tokio::spawn(async move {
let url = Url::from("https://rustlang.org");
let _body = service.fetch(url).await;
});
}
如果特征
上没有Send
约束,则无法编译
,并显示错误:"不能在线程间安全发送未来"
.用Send
约束创建特征的变量
,可避免发送用户
掉此陷阱
.
注意,如果未公开你的特征
,则不会看到警告
,因为如果有问题,总是可自行
添加发送
约束.
见此博客文章.
我可插件使用async fn
和impl Trait
吗
是的,你可以在特征
和实现
中的async fn
和->implFuture
拼写间自由切换
.即使一个形式
有发送
约束,因此.这样更易使用trait_variant
创建的特征.
trait HttpService: Send {
fn fetch(&self, url: Url)
-> impl Future<Output = HtmlBody> + Send;
}
impl HttpService for MyService {
async fn fetch(&self, url: Url) -> HtmlBody {
//只要有`'do_fetch():Send'`就可以了!
self.client.do_fetch(url).await.into_body()
}
}
为什么这些签名不使用impl Future+'_
对特征中的->impl Trait
,提前用了2024
年的抓规则
.即今天经常看到的+'_
,在特征
中是不必要的,因为已假设中
类型来抓输入生命期
.
在2024
版中,此规则针对所有函数签名
.
为什么在使用->impl Trait
实现特征时收到"细化"
警告
如果你的实现
签名,包含比特征
自身更详细的信息
,你会收到警告
:
pub trait Foo {
fn foo(self) -> impl Debug;
}
impl Foo for u32 {
fn foo(self) -> String {
//^^^^^^警告:`实现方法签名`中的`impl Trait`与`trait`方法签名不匹配
self.to_string()
}
}
原因是你可能泄露
了更多实现细节
.如,如果以下代码编译
.
fn main() {
//实现者允许使用`'显示'`,还是只允许使用`特征`所说的`'调试'`
println!("{}", 32.foo());
}
因为细化了特征
实现,它确实可编译,但编译器会要求你在实现
上使用#[allow(refining_impl_trait)]
,确认你打算细化特征
接口.
注意,只能在可以命名类型
时,才能使用关联类型
.一旦impl_trait_in_assoc_type
稳定下来,才取消此限制
.
这是因为允许知识
从未指定签名
它们的项目中"泄漏
"的auto trait
泄漏.