Skip to content

Commit b096d46

Browse files
authored
doc: add release note for volo 0.8.0 (#835)
1 parent 76cd3c5 commit b096d46

File tree

2 files changed

+306
-0
lines changed

2 files changed

+306
-0
lines changed
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
---
2+
title: 'Volo Release 0.8.0'
3+
linkTitle: 'Release v0.8.0'
4+
projects: ["Volo"]
5+
date: 2023-10-23
6+
description: >
7+
---
8+
9+
In Volo 0.8.0, we mainly refactored the Service trait and all previous places that used async_trait by using two newly stabilized features: AFIT (Async Fn In Trait) and RPITIT (Return Position Impl Trait In Traits). This not only brings a slight performance improvement, but also significantly enhances the usability of writing Service, as you can directly write async fn call.
10+
11+
## Break Change
12+
13+
### Service trait refactoring
14+
15+
In the latest nightly, Rust's two highly anticipated heavyweight features, AFIT (Async Fn In Trait) and RPITIT (Return Position Impl Trait In Traits), have been stabilized, which means that in two months, we can use volo in stable rust.
16+
17+
Here's a brief introduction to these two features:
18+
19+
#### RPITIT
20+
21+
RPITIT means that we can now write impl trait in the return position of a function inside a trait, whereas previously we could only write it in regular functions, such as:
22+
23+
```rust
24+
fn fetch(key: FastStr) -> impl Future<Output = Result<Item>>
25+
```
26+
27+
Now, we can write it directly inside a trait:
28+
29+
```rust
30+
trait Fetcher {
31+
fn fetch(&self, key: FastStr) -> impl Future<Output = Result<Item>>;
32+
}
33+
```
34+
35+
#### AFIT
36+
37+
AFIT's feature is that we can now define async fn directly in a trait (essentially syntactic sugar for RPITIT) and no longer need to use the #[async_trait] macro, for example:
38+
39+
```rust
40+
trait Fetcher {
41+
async fn fetch(&self, key: FastStr) -> Result<Item>;
42+
}
43+
```
44+
45+
In fact, it's just syntactic sugar, and the compiler will convert this async fn into the form of RPITIT mentioned above.
46+
47+
For more information, please refer to: https://github.com/rust-lang/rust/pull/115822
48+
49+
#### New Service definition
50+
51+
The original definition of the new Service Trait is as follows:
52+
53+
```rust
54+
pub trait Service<Cx, Request> {
55+
/// Responses given by the service.
56+
type Response;
57+
/// Errors produced by the service.
58+
type Error;
59+
60+
/// Process the request and return the response asynchronously.
61+
fn call<'s, 'cx>(
62+
&'s self,
63+
cx: &'cx mut Cx,
64+
req: Request,
65+
) -> impl Future<Output = Result<Self::Response, Self::Error>> + Send;
66+
}
67+
```
68+
69+
A more understandable definition is as follows, and you can understand it directly:
70+
71+
```rust
72+
pub trait Service<Cx, Request> {
73+
/// Responses given by the service.
74+
type Response;
75+
/// Errors produced by the service.
76+
type Error;
77+
78+
/// Process the request and return the response asynchronously.
79+
async fn call<'s, 'cx>(
80+
&'s self,
81+
cx: &'cx mut Cx,
82+
req: Request,
83+
) -> Result<Self::Response, Self::Error>;
84+
}
85+
```
86+
87+
Compared to the previous definition, the type Future associated type is removed, and the order of lifetimes in call is changed (the previous order was `call<'cx, 's>`, which was a typo when it was first written, and now it's changed back).
88+
89+
#### Migration Guide
90+
91+
1. Update Rust compiler to the latest nightly (rustup update) and all dependencies (volo, pilota, motore) to the latest version
92+
2. Run cargo check to see where the errors are, such as "type Future is not a member", "associated type Future not found", etc. We will use the following `Service` as an example:
93+
```rust
94+
impl<Cx, Req, S> Service<Cx, Req> for LogService<S>
95+
where
96+
S: Service<Cx, Req> + Send + 'static + Sync,
97+
Cx: Context<Config = volo_grpc::context::Config> + 'static + Send,
98+
Req: Send + 'static,
99+
{
100+
type Response = S::Response;
101+
102+
type Error = S::Error;
103+
104+
type Future<'cx> = impl Future<Output = Result<Self::Response, Self::Error>> + 'cx;
105+
106+
fn call<'cx, 's>(&'s self, cx: &'cx mut Cx, req: Req) -> Self::Future<'cx>
107+
where
108+
's: 'cx,
109+
{
110+
async move {
111+
let tick = quanta::Instant::now();
112+
let ret = self.inner.call(cx, req).await;
113+
let elapsed = quanta::Instant::now().duration_since(tick);
114+
tracing::info!(rpc_type = "rpcAccess", cost = elapsed.as_micros() as i64);
115+
ret
116+
}
117+
}
118+
}
119+
```
120+
3. Remove the line with `type Future`
121+
4. Swap the lifetimes in `fn call<'cx, 's>` and remove the `where` statement below
122+
5. Add `async` before `fn call`, then change `Self::Future<'cx>` to `Result<Self::Response, Self::Error>`, and remove the `async move` in the function body
123+
6. The final modified `Service` is as follows:
124+
```rust
125+
impl<Cx, Req, S> Service<Cx, Req> for LogService<S>
126+
where
127+
S: Service<Cx, Req> + Send + 'static + Sync,
128+
Cx: Context<Config = volo_grpc::context::Config> + 'static + Send,
129+
Req: Send + 'static,
130+
{
131+
type Response = S::Response;
132+
133+
type Error = S::Error;
134+
135+
async fn call<'s, 'cx>(&'s self, cx: &'cx mut Cx, req: Req) -> Result<Self::Response, Self::Error> {
136+
let tick = quanta::Instant::now();
137+
let ret = self.inner.call(cx, req).await;
138+
let elapsed = quanta::Instant::now().duration_since(tick);
139+
tracing::info!(rpc_type = "rpcAccess", cost = elapsed.as_micros() as i64);
140+
ret
141+
}
142+
}
143+
```
144+
145+
### Handler refactoring
146+
147+
In previous versions, Volo generated user handlers using async_trait for ease of use, but thanks to AFIT, we can now write async fn directly in traits, so we removed async trait (reducing one Box overhead).
148+
149+
Therefore, after upgrading, you may initially encounter errors like "lifetime parameters or bounds not match". In this case, simply remove the `#[async_trait]` macro.
150+
151+
## Complete Release Note
152+
153+
For the complete Release Note, please refer to: [Volo Changelog](https://github.com/cloudwego/volo/compare/volo-0.5.4...volo-0.8.0)
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
---
2+
title: 'Volo 0.8.0 版本发布'
3+
linkTitle: 'Release v0.8.0'
4+
projects: ["Volo"]
5+
date: 2023-10-23
6+
description: >
7+
---
8+
9+
Volo 0.8.0 版本中,我们主要使用了 AFIT(Async Fn In Trait) 和 RPITIT(Return Position Impl Trait In Traits) 这两个刚刚 stabilized 的 feature 重构了 Service trait 和之前所有用了 async_trait 的地方,除了会有略微的性能提升外,更重要的是,接下来写 Service 可以直接写 async fn call 了,易用性会有较大的提升。
10+
11+
## Break Change
12+
13+
### Service trait 重构
14+
15+
在最新的 nightly 中,Rust 备受瞩目的两个重量级 feature AFIT(Async Fn In Trait)和 RPITIT(Return Position Impl Trait In Traits)已经稳定了,也就意味着两个月后,我们就可以在 stable rust 中使用 volo 了。
16+
17+
这里先简单介绍一下这两个 feature:
18+
19+
#### RPITIT
20+
21+
RPITIT 的意思是,我们可以在 trait 里面,在函数返回的地方写 impl trait 了,之前我们只能在普通的函数里面写,比如:
22+
23+
```rust
24+
fn fetch(key: FastStr) -> impl Future<Output = Result<Item>>
25+
```
26+
27+
而现在,我们可以直接在 trait 里面写了:
28+
29+
```rust
30+
trait Fetcher {
31+
fn fetch(&self, key: FastStr) -> impl Future<Output = Result<Item>>;
32+
}
33+
```
34+
35+
#### AFIT
36+
37+
AFIT 的功能就是,我们可以直接在 trait 里面定义 async fn 了(其实本质上是 RPITIT 的语法糖),并且不需要使用 #[async_trait] 这个宏了,比如:
38+
39+
```rust
40+
trait Fetcher {
41+
async fn fetch(&self, key: FastStr) -> Result<Item>;
42+
}
43+
```
44+
45+
实际上,也就是一个语法糖,编译器会将这个 async fn 转换成上述的 RPITIT 的形式。
46+
47+
如需了解更多,可以参考:https://github.com/rust-lang/rust/pull/115822
48+
49+
#### Service 新定义
50+
51+
新版 Service Trait 的原始定义如下:
52+
53+
```rust
54+
pub trait Service<Cx, Request> {
55+
/// Responses given by the service.
56+
type Response;
57+
/// Errors produced by the service.
58+
type Error;
59+
60+
/// Process the request and return the response asynchronously.
61+
fn call<'s, 'cx>(
62+
&'s self,
63+
cx: &'cx mut Cx,
64+
req: Request,
65+
) -> impl Future<Output = Result<Self::Response, Self::Error>> + Send;
66+
}
67+
```
68+
69+
一个更容易理解的定义是这样的,大家直接这么理解即可:
70+
71+
```rust
72+
pub trait Service<Cx, Request> {
73+
/// Responses given by the service.
74+
type Response;
75+
/// Errors produced by the service.
76+
type Error;
77+
78+
/// Process the request and return the response asynchronously.
79+
async fn call<'s, 'cx>(
80+
&'s self,
81+
cx: &'cx mut Cx,
82+
req: Request,
83+
) -> Result<Self::Response, Self::Error>;
84+
}
85+
```
86+
87+
与之前的定义对比,去掉了 type Future 关联类型,同时修改了 call 中生命周期的顺序(之前的顺序为`call<'cx, 's>`,是一开始写的时候 typo 写反了,趁这个机会改回来)。
88+
89+
#### 迁移指南
90+
91+
1. Rust 编译器更新到最新 nightly(rustup update)及所有依赖(volo、pilota、motore)升级到最新版
92+
2. cargo check 看看哪里报错,可能会遇到比如`type Future is not a member``associated type Future not found`等类似错误,我们以如下`Service`为例:
93+
```rust
94+
impl<Cx, Req, S> Service<Cx, Req> for LogService<S>
95+
where
96+
S: Service<Cx, Req> + Send + 'static + Sync,
97+
Cx: Context<Config = volo_grpc::context::Config> + 'static + Send,
98+
Req: Send + 'static,
99+
{
100+
type Response = S::Response;
101+
102+
type Error = S::Error;
103+
104+
type Future<'cx> = impl Future<Output = Result<Self::Response, Self::Error>> + 'cx;
105+
106+
fn call<'cx, 's>(&'s self, cx: &'cx mut Cx, req: Req) -> Self::Future<'cx>
107+
where
108+
's: 'cx,
109+
{
110+
async move {
111+
let tick = quanta::Instant::now();
112+
let ret = self.inner.call(cx, req).await;
113+
let elapsed = quanta::Instant::now().duration_since(tick);
114+
tracing::info!(rpc_type = "rpcAccess", cost = elapsed.as_micros() as i64);
115+
ret
116+
}
117+
}
118+
}
119+
```
120+
3.`type Future`这行直接去掉
121+
4.`fn call<'cx, 's>`中的生命周期位置对调,并去掉下面的`where`语句
122+
5.`fn call`前面加个`async`,然后把`Self::Future<'cx>`这部分,改成`Result<Self::Response, Self::Error>`,并去掉函数体里面的`async move`
123+
6. 最终改完的`Service`如下:
124+
```rust
125+
impl<Cx, Req, S> Service<Cx, Req> for LogService<S>
126+
where
127+
S: Service<Cx, Req> + Send + 'static + Sync,
128+
Cx: Context<Config = volo_grpc::context::Config> + 'static + Send,
129+
Req: Send + 'static,
130+
{
131+
type Response = S::Response;
132+
133+
type Error = S::Error;
134+
135+
async fn call<'s, 'cx>(&'s self, cx: &'cx mut Cx, req: Req) -> Result<Self::Response, Self::Error> {
136+
let tick = quanta::Instant::now();
137+
let ret = self.inner.call(cx, req).await;
138+
let elapsed = quanta::Instant::now().duration_since(tick);
139+
tracing::info!(rpc_type = "rpcAccess", cost = elapsed.as_micros() as i64);
140+
ret
141+
}
142+
}
143+
```
144+
145+
### Handler 重构
146+
147+
之前版本中,Volo 生成的用户的 handler 为了易用性使用了 async_trait,但得益于 AFIT,现在我们可以直接在 trait 中写 async fn 了,因此我们去掉了 async trait(可以减少一次 Box 开销)。
148+
149+
因此,升级后可能一开始会遇到类似“lifetime parameters or bounds not match”的错误,遇到这种问题直接去掉`#[async_trait]`宏即可。
150+
151+
## 完整 Release Note
152+
153+
完整的 Release Note 可以参考:[Volo Changelog](https://github.com/cloudwego/volo/compare/volo-0.5.4...volo-0.8.0)

0 commit comments

Comments
 (0)