How to understand the ownership principle of struct m here?

use anyhow::Result as AnyResult;
#[derive(Debug)]
struct Value(i32);

#[tokio::main]
async fn main() -> AnyResult<()> {
    let mut m = Value(1); 
    async move {
        m.0 = 123;
    }
    .await;
    println!("m: {:?}", m);  //why output: m: Value(1)
    Ok::<_, anyhow::Error>(())
}

Why?

This is an example of disjoint capture in closures.

The i32 field is Copy, so the async move block is mutating a copy. If you replace the inner type with one that is not Copy, you will get a partial move error:

As expected, you can replace the async move block with a move closure that has the same effect.

fn main() {
    let mut m = Value(1);
    (move || {
        m.0 = 123;
    })();
    println!("m: {m:?}");
}
2 Likes

And if you wanted to mutate in place, don't use move. Or if you need move for other reasons, create &mut _ to certain fields and use those instead.

2 Likes

Thanks,

  1. struct Value(i32) is a simple tuple struct wrapping an i32.
  2. By default, types in Rust are not Copy unless explicitly derived with #[derive(Copy, Clone)] or they consist solely of Copy fields and opt into Copy. Here, Value does not derive Copy, so it is indeed a non-Copy type, meaning it follows move semantics rather than copy semantics.

I am confused that the code defines Value(i32) without Copy, so it should be non-Copy. Rust compiler should not treat it as Copy type implicitly.

Also by Ownership Rules, with async move, the code should fail to compile. However, the compile result is no error, and the output is m: Value(1).

It is a bit tough to know about disjoint capture.:smiling_face_with_tear:

The Value struct itself is indeed not Copy. However, the disjoint capture section of the Edition Guide (and linked RFC) explains that the closure does not capture the whole Value struct. It only capture the fields that it uses. In this case, the field is Copy and the closure is move.

If you compile with Edition 2018 or earlier, it will fail. The mental model you describe fits the implementation in these older editions, before disjoint capture in closures was added.

2 Likes

Great :+1:, got it. Thanks

like this:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=d94291298dc9315a2805539366667593

use anyhow::Result as AnyResult;
#[derive(Debug)]
struct Value(i32);

#[tokio::main]
async fn main() -> AnyResult<()> {
    let mut m = Value(1); 
    async {
        m.0 = 123;
    }
    .await;
    println!("m: {:?}", m); 
    let m0 = &mut m.0;
    async move {
        *m0 = 234; 
    }.await;
    println!("m: {:?}", m);
    Ok::<_, anyhow::Error>(())
}
1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.