Я только что узнал, что мы можем использовать #[rustc_layout(debug)]
для выгрузки макета шрифта из этого поста в блоге . И я сразу же пытаюсь использовать его, чтобы увидеть сгенерированное будущее.
Вот пример: (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),
}
В макете есть не только поля, но и варианты. Вот мне и интересно, что это означает? Это похоже на union
struct
, который хранит асинхронные аргументы fn, и 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.