我刚刚了解到我们可以使用
#[rustc_layout(debug)]
从这篇博客文章中转储类型布局。我立即尝试用它来看穿生成的 future。
这是一个例子:(Rust Playground)
#![feature(rustc_attrs)]
#![feature(type_alias_impl_trait)]
use std::future::Future;
use std::time::Duration;
use tokio::sync::{mpsc::UnboundedSender, oneshot::Receiver};
struct Actor {}
impl Actor {
async fn act(&self, mut stop_listener: Receiver<()>, result_channel: UnboundedSender<u64>) {
let part_1_jh = async {
loop {
if stop_listener.try_recv().is_ok() {
return;
}
println!("Hello from p1");
tokio::time::sleep(Duration::from_millis(400)).await;
}
};
let part_2_jh = async {
loop {
println!("Hello from p2");
result_channel.send(43).unwrap();
tokio::time::sleep(Duration::from_millis(700)).await;
}
};
tokio::join!(part_1_jh, part_2_jh);
}
}
#[rustc_layout(debug)]
type Fut<'a> = impl Future;
fn foo(this: &Actor, stop_listener: Receiver<()>, result_channel: UnboundedSender<u64>) -> Fut {
this.act(stop_listener, result_channel)
}
下面是转储的布局:
error: layout_of({async fn body of Actor::act()}) = Layout {
size: Size(320 bytes),
align: AbiAndPrefAlign {
abi: Align(8 bytes),
pref: Align(8 bytes),
},
abi: Aggregate {
sized: true,
},
fields: Arbitrary {
offsets: [
Size(296 bytes),
Size(272 bytes),
Size(304 bytes),
Size(313 bytes),
],
memory_index: [
1,
0,
2,
3,
],
},
largest_niche: Some(
Niche {
offset: Size(313 bytes),
value: Int(
I8,
false,
),
valid_range: 0..=3,
},
),
variants: Multiple {
tag: Initialized {
value: Int(
I8,
false,
),
valid_range: 0..=3,
},
tag_encoding: Direct,
tag_field: 3,
variants: [
Layout {
size: Size(320 bytes),
align: AbiAndPrefAlign {
abi: Align(8 bytes),
pref: Align(8 bytes),
},
abi: Aggregate {
sized: true,
},
fields: Arbitrary {
offsets: [],
memory_index: [],
},
largest_niche: None,
variants: Single {
index: 0,
},
max_repr_align: None,
unadjusted_abi_align: Align(8 bytes),
},
Layout {
size: Size(320 bytes),
align: AbiAndPrefAlign {
abi: Align(8 bytes),
pref: Align(8 bytes),
},
abi: Aggregate {
sized: true,
},
fields: Arbitrary {
offsets: [],
memory_index: [],
},
largest_niche: None,
variants: Single {
index: 1,
},
max_repr_align: None,
unadjusted_abi_align: Align(8 bytes),
},
Layout {
size: Size(320 bytes),
align: AbiAndPrefAlign {
abi: Align(8 bytes),
pref: Align(8 bytes),
},
abi: Aggregate {
sized: true,
},
fields: Arbitrary {
offsets: [],
memory_index: [],
},
largest_niche: None,
variants: Single {
index: 2,
},
max_repr_align: None,
unadjusted_abi_align: Align(8 bytes),
},
Layout {
size: Size(320 bytes),
align: AbiAndPrefAlign {
abi: Align(8 bytes),
pref: Align(8 bytes),
},
abi: Aggregate {
sized: true,
},
fields: Arbitrary {
offsets: [
Size(280 bytes),
Size(288 bytes),
Size(0 bytes),
Size(256 bytes),
Size(312 bytes),
],
memory_index: [
2,
3,
0,
1,
4,
],
},
largest_niche: None,
variants: Single {
index: 3,
},
max_repr_align: None,
unadjusted_abi_align: Align(8 bytes),
},
],
},
max_repr_align: None,
unadjusted_abi_align: Align(8 bytes),
}
布局不仅有字段,还有变体。所以我想知道它代表什么?它是否像存储异步 fn 参数的
union
和存储异步状态的 struct
的 enum
?
是的,生成的未来类型具有与未来状态相对应的变体。据我所知,它没有变体之外的任何数据。您可以使用
-Zprint-type-sizes
获取有关类型的更多信息,特别是包括变体的名称和(在某些情况下,由 我自己的贡献)字段的类型。第一个列出的也是最大的类型是 act()
本身:
type: `{async fn body of Actor::act()}`: 288 bytes, alignment: 8 bytes
discriminant: 1 bytes
variant `Unresumed`: 280 bytes
padding: 239 bytes
upvar `.stop_listener`: 8 bytes, alignment: 8 bytes
padding: 16 bytes
upvar `.self`: 8 bytes, alignment: 8 bytes
upvar `.result_channel`: 8 bytes
variant `Suspend0`: 281 bytes
local `.futures`: 224 bytes, offset: 0 bytes, alignment: 8 bytes
local `.__awaitee`: 16 bytes, type: tokio::future::poll_fn::PollFn<{closure@/Users/kpreid/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.37.0/src/macros/5:26}>
upvar `.stop_listener`: 8 bytes
local `.stop_listener`: 8 bytes
local `.result_channel`: 8 bytes
upvar `.self`: 8 bytes
upvar `.result_channel`: 8 bytes
local `..coroutine_field4`: 1 bytes, type: bool
variant `Returned`: 280 bytes
padding: 239 bytes
upvar `.stop_listener`: 8 bytes, alignment: 8 bytes
padding: 16 bytes
upvar `.self`: 8 bytes, alignment: 8 bytes
upvar `.result_channel`: 8 bytes
variant `Panicked`: 280 bytes
padding: 239 bytes
upvar `.stop_listener`: 8 bytes, alignment: 8 bytes
padding: 16 bytes
upvar `.self`: 8 bytes, alignment: 8 bytes
upvar `.result_channel`: 8 bytes
end padding: 6 bytes
在此您可以看到初始状态(
Unresumed
)具有捕获的变量,并且第一个且唯一的Suspend*
状态必须对应于等待tokio::join!
的实现。 Returned
状态是未来返回Poll::Ready
之后的状态。我不确定 Panicked
状态的用途是什么,但大概是在轮询未来恐慌时留下的状态。
请注意,各个变体中都散布着“填充”;这里发生的情况是,字段经过排列,以便从一种状态转换到另一种状态不需要将字段中的数据复制到新位置。
在报告的更下方,您可以找到内部异步块:
type: `{async block@src/lib.rs:12:25: 20:10}`: 112 bytes, alignment: 8 bytes
discriminant: 1 bytes
variant `Unresumed`: 8 bytes
upvar `._ref__stop_listener`: 8 bytes, offset: 0 bytes, alignment: 8 bytes
variant `Suspend0`: 104 bytes
upvar `._ref__stop_listener`: 8 bytes, offset: 0 bytes, alignment: 8 bytes
local `.__awaitee`: 96 bytes, type: Sleep
variant `Returned`: 8 bytes
upvar `._ref__stop_listener`: 8 bytes, offset: 0 bytes, alignment: 8 bytes
variant `Panicked`: 8 bytes
upvar `._ref__stop_listener`: 8 bytes, offset: 0 bytes, alignment: 8 bytes
end padding: 7 bytes
type: `{async block@src/lib.rs:22:25: 28:10}`: 112 bytes, alignment: 8 bytes
discriminant: 1 bytes
variant `Unresumed`: 8 bytes
upvar `._ref__result_channel`: 8 bytes, offset: 0 bytes, alignment: 8 bytes
variant `Suspend0`: 104 bytes
upvar `._ref__result_channel`: 8 bytes, offset: 0 bytes, alignment: 8 bytes
local `.__awaitee`: 96 bytes, type: Sleep
variant `Returned`: 8 bytes
upvar `._ref__result_channel`: 8 bytes, offset: 0 bytes, alignment: 8 bytes
variant `Panicked`: 8 bytes
upvar `._ref__result_channel`: 8 bytes, offset: 0 bytes, alignment: 8 bytes
end padding: 7 bytes
同样,其中每一个都只有一个
await
,因此它们只有一个 Suspend
状态,正在等待 tokio::time::Sleep
实例。