Strange compiler behavior - modifying a moved value read in another place


#1

I found a strange behavior of rustc. In the following codes, setting val.modify to true is allowed by rustc, but val have moved into vals. Though this modification has no effect. Can someone explain that why rustc doesn’t complain about it?

#[derive(Debug)]
struct A {
    modify: bool,
}

fn do_something(inputs: &[A]) {
    println!("In do_something, inputs: {:?}", inputs);
}

fn main() {
    let mut val = A { modify: false };
    // val should moved here
    let vals = vec![val];
    do_something(&vals);
    // But we still can modify it
    // Maybe rustc should complain about this?
    val.modify = true;
    // If you want to read val again, rustc will complain about that
    // if val.modify {
    // }
    // Though your modification won't work
    do_something(&vals);
}

Output:

In do_something, inputs: [A { modify: false }]
In do_something, inputs: [A { modify: false }]

Playground:

https://play.rust-lang.org/?gist=6b3aec78bb290b70c7aae879792821e5&version=beta&mode=debug&edition=2015


#2

Looks like the compiler is willing to ignore that line because it can see that it does nothing? If you try to use val (i.e. do something like, println!("{:?}", val);) the compiler will complain that val is moved.


#3

Just because you’ve moved the old value out doesn’t preclude you from writing a new value into the same storage location.

Also, modifying val has no effect on vals, because that’s not how the language works at all. Modifying a value in one place cannot have any effect on values in other places. You would need a pointer of some kind for that, but val isn’t a pointer.

Edit: huh, I could have sworn the compiler let you overwrite fields to reconstruct a structure after it had been moved. I was wrong. You can still do this, but you have to replace the entire structure.


#4

Yes, reading the moved val is not allowed, but I think it would be better if the compiler notify that we are writing to a moved value.


#5

Maybe complaining this meaningless writing to a moved value would be better than ignoring it


#6

Here’s the thing: a structure is just a collection of values. You can overwrite the storage of a moved value just fine. By extension, you should logically be able to overwrite the storage of a moved struct one field at a time.

Unless there’s some compelling reason not to, I think it’d make more sense to allow this to work.


#7

A couple of interesting things.

I tried to write in the location using write_volatile, at this point the compiler complains. I imagine that once the operation can be optimized away, for the compiler everything is fine.

Second note: clippy does not say anything as well.


#8

For example, this already works:

#[derive(Debug)]
struct A(i32);

#[derive(Debug)]
struct B {
    x: A,
    y: A,
}

fn main() {
    let mut val = B { x: A(1), y: A(2) };
    println!("{:?}", val);
    let x = val.x;
    println!("{:?}", x);
    val.x = A(3);
    println!("{:?}", val);
}

#9

Consider something like the following:

let mut vals = vec![];
let mut v = 12i32;
for i in 1..100 {
    vals.push(v);
    v = i;
}

Here we are moving a value and the writing to it in a fairly mundane way.


#11

Nice explanation, Thanks!


#12

Oh, I forgot the most interesting part, if you try to read your modified value, rustc will complain about it. But if you just write it without reading it, rustc will ignore it. So you are allowed to write to a value but you can’t read it anymore.

fn main() {
    let mut val = A { modify: false };
    // val should moved here
    let vals = vec![val];
    do_something(&vals);
    // But we still can modify it
    val.modify = true;
    // You will get an error:
    if val.modify {
        println!("val: {:?}", val);
    }
    // Though your modification won't work
    do_something(&vals);
}

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

@skyschermer @DanielKeep


#13

This is a known issue: