Description
Hi! Recently we have encountered an issue for metrics reporting. We are using futures_async_stream::try_stream to convert our iterators into stream, and we implement a struct Stats
to collect useful metrics and report the metrics when Stats
gets dropped.
The try_stream
macro generates the internal code of the stream by using rust Generator
, and the Stats
is mutably moved in the generator closure, and will get modified while collecting the metrics. Under our expectation, we are able to collect some correct metrics before the Stats
gets dropped. However, in our observation, the Stats
is not modified at all, and none of the metrics is collected.
After some experiment, we figure out that there is a confusing behavior in the rust code. We have minimized our code, and the following code is a minimum reproducible code for the unexpected behavior.
I tried this code:
#![feature(generators, generator_trait)]
use std::ops::Generator;
use std::pin::Pin;
#[derive(Default, Debug)]
struct SomethingElse {
num: usize,
}
#[derive(Default, Debug)]
struct TestStats {
total_items: usize,
some: SomethingElse,
}
impl Drop for TestStats {
fn drop(&mut self) {
println!("dropped: {} {:?}", self.total_items, self.some);
}
}
pub fn main() {
let mut stats = TestStats::default();
let mut generator = Box::pin(move || {
// println!("{:?}", stats);
stats.total_items += 1;
yield 2;
});
Pin::new(&mut generator).resume(());
}
We expected to see this output:
dropped: 1 SomethingElse { num: 0 }
which means that stats.total_items += 1
gets executed once.
Instead, we saw
dropped: 0 SomethingElse { num: 0 }
which means that stats.total_items += 1
did not get executed on stats.total_items
.
After we uncomment the line println!("{:?}", stats);
and reference the whole stats
,
#![feature(generators, generator_trait)]
use std::ops::Generator;
use std::pin::Pin;
#[derive(Default, Debug)]
struct SomethingElse {
num: usize,
}
#[derive(Default, Debug)]
struct TestStats {
total_items: usize,
some: SomethingElse,
}
impl Drop for TestStats {
fn drop(&mut self) {
println!("dropped: {} {:?}", self.total_items, self.some);
}
}
pub fn main() {
let mut stats = TestStats::default();
let mut generator = Box::pin(move || {
println!("{:?}", stats); // Previously commented !!!
stats.total_items += 1;
yield 2;
});
Pin::new(&mut generator).resume(());
}
, the output gets as expected.
TestStats { total_items: 0, some: SomethingElse { num: 0 } }
dropped: 1 SomethingElse { num: 0 }
Meta
The code can be run on Rust Playground.
<version>
1.70.0-nightly 2023-03-05 7820b62
Backtrace
<backtrace>