2017-10-19 9 views
14

Pourquoi n1_mut est-il toujours valide dans cet exemple? Il a été déplacé dans Option::Some alors ne devrait-il pas être invalide?Pourquoi l'affectation à un membre d'un pointeur est-elle toujours valide après le déplacement du pointeur?

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)); 
} 

Ce qui suit ne compile pas avec le familier « l'utilisation de la valeur déplacée » Erreur:

#[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 
+0

Intéressant. Je ne suis pas sûr que cela compte comme un bug - je ne pense pas que vous pouvez induire en erreur parce qu'il n'y a aucun moyen de lire la mémoire par la suite. Mais si vous gardez un pointeur brut sur la pile, vous pouvez dire que 'n1Mut.next' est bien défini: https://play.rust-lang.org/?gist=d41422bfd142c289667e7c2fb3183be0&version=undefined – trentcl

+0

Il est intéressant de noter que ce n'est pas possible utilisez 'n1_mut.next' après. En outre, l'ajout d'une implémentation 'Drop' provoque:" error [E0383]: réinitialisation partielle de la structure non initialisée "n1_mut'" –

Répondre

8

Cela n'a rien à voir avec un pointeur ou non; cela fonctionne ainsi:

#[derive(Debug)] 
struct NonCopy; 

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

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

    drop(foo); 

    foo.name = NonCopy; 
} 

Bien que je ne trouve pas la même question pour que je sais que je l'ai vu avant, ce quote from nikomatsakis décrit:

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".

Il y a aussi la discussion sur the Rust subreddit, qui liens vers Rust issue 21232: "borrow-checker allows partial reinit of struct that has been moved away, but no use of it"

Conceptuellement, il y a un drapeau pour chacun des champs dans une structure en plus de la structure elle-même - j'aime penser à Chris Morgan's cardboard box analogy. Vous pouvez sortir d'un champ de struct appartenant aussi longtemps que vous reculez avant d'utiliser la struct:

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

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

De toute évidence, depuis 2014, personne n'a pris la peine de faire l'effort pour permettre marquer l'ensemble struct comme valide encore une fois une fois les champs sont remplis. De manière réaliste, vous n'avez pas vraiment besoin de cette fonctionnalité, car vous pouvez simplement affecter la totalité de la variable en une seule fois. L'implémentation actuelle est trop sûre car Rust vous empêche de faire quelque chose qui semble correct.