Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is assigning to a member of a pointer still valid after the pointer is moved?

Tags:

rust

Why is n1_mut still valid in this example? It has been moved into Option::Some so shouldn't it be invalid?

struct MyRecordRec2<'a> {
    pub id: u32,
    pub name: &'a str,
    pub next: Box<Option<MyRecordRec2<'a>>>
}

#[test]
fn creating_circular_recursive_data_structure() {
    let mut n1_mut = MyRecordRec2 {
        id: 1,
        name: "n1",
        next: Box::new(None)
    };

    let n2 = MyRecordRec2 {
        id: 2,
        name: "n2",
        next: Box::new(Some(n1_mut))
    };

    //Why is n1_mut still valid?
    n1_mut.next = Box::new(Some(n2));
}

The following does not compile with the familiar "use of moved value" error:

#[test]
fn creating_and_freezing_circular_recursive_data_structure() {
    let loop_entry = {
        let mut n1_mut = MyRecordRec2 {
            id: 1,
            name: "n1",
            next: Box::new(None),
        };

        let n2 = MyRecordRec2 {
            id: 2,
            name: "n2",
            next: Box::new(Some(n1_mut)),
        };

        n1_mut.next = Box::new(Some(n2));

        n1_mut
    };
}
error[E0382]: use of moved value: `n1_mut`
  --> src/main.rs:44:9
   |
39 |             next: Box::new(Some(n1_mut)),
   |                                 ------ value moved here
...
44 |         n1_mut
   |         ^^^^^^ value used here after move
   |
   = note: move occurs because `n1_mut` has type `MyRecordRec2<'_>`, which does not implement the `Copy` trait
like image 666
user3284063 Avatar asked Oct 19 '17 15:10

user3284063


1 Answers

This doesn't have anything to do with being a pointer or not; this works as well:

#[derive(Debug)]
struct NonCopy;

#[derive(Debug)]
struct Example {
    name: NonCopy,
}

fn main() {
    let mut foo = Example {
        name: NonCopy,
    };

    drop(foo);

    foo.name = NonCopy;
}

Although I can't find the similar SO question that I know I've seen before, this quote from nikomatsakis describes it:

In general moves are tracked at a pretty narrow level of granularity. We intend to eventually permit you to "fill" both fields back in and then use the structure again. I guess that doesn't work today. I have to go look again at the moves code, but I think in general one of the things I'd like to pursue post 1.0 is extending the type system to deal better with things that have been moved from (in particular I want to support moves out of &mut pointers, so long as you restore the value before doing anything fallible). Anyway I think this example more-or-less falls out of treating things in a general way, though you could imagine rules that say "if you move f, you can never again touch any subfields of f without restoring f as a unit".

There's also discussion on the Rust subreddit, which links to Rust issue 21232: "borrow-checker allows partial reinit of struct that has been moved away, but no use of it"

Conceptually, there's a flag for each of the fields in a struct in addition to the struct itself — I like to think of Chris Morgan's cardboard box analogy. You can move out of an owned struct's field so long as you move back in before using the struct:

drop(foo.name);
foo.name = NonCopy;

println!("{:?}", foo);

Evidently, since 2014, no one has bothered to put in the effort to enable marking the entire struct as valid again once the fields are re-filled.

Realistically, you don't really need this functionality as you can just assign the entire variable at once. The current implementation is overly-safe as Rust is preventing you from doing something that seems OK.

like image 73
Shepmaster Avatar answered Sep 29 '22 18:09

Shepmaster



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!