Skip to content

Unexpected partial move in closure for Copy field #108808

Open
@wenym1

Description

@wenym1

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>

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-edition-2021Area: The 2021 editionC-bugCategory: This is a bug.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions