2017-10-19 59 views
14

Dlaczego numer n1_mut jest nadal ważny w tym przykładzie? Zostało przeniesione do Option::Some, więc czy nie powinno być ono nieważne?Dlaczego przypisywanie do elementu wskaźnika jest nadal poprawne po przeniesieniu wskaźnika?

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

Następujące nie skompilować ze znanego „wykorzystania przeniósł wartości” Błąd:

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

Interesujące. Nie jestem pewien, czy to się liczy jako błąd - nie sądzę, że możesz wzbudzić brak bezpieczeństwa, ponieważ nie ma sposobu, aby później odczytać pamięć. Ale jeśli utrzymujesz surowy wskaźnik na stosie, możesz powiedzieć, że 'n1Mut.next' faktycznie się ustawia: https://play.rust-lang.org/?gist=d41422bfd142c289667e7c2fb3183be0&version=undefined – trentcl

+0

Co ciekawe, nie można nawet użyj po 'n1_mut.next'. Dodanie implementacji 'Drop' powoduje:" błąd [E0383]: częściowa reinicjalizacja niezainicjowanej struktury 'n1_mut'" –

Odpowiedz

8

To nie ma nic wspólnego z byciem wskaźnik lub nie; to działa tak samo:

#[derive(Debug)] 
struct NonCopy; 

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

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

    drop(foo); 

    foo.name = NonCopy; 
} 

Chociaż nie mogę znaleźć podobne pytanie SO, że wiem, że nie widział, to quote from nikomatsakis opisuje go:

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

Istnieje również dyskusja na the Rust subreddit, który Łącza do Rust issue 21232: "borrow-checker allows partial reinit of struct that has been moved away, but no use of it"

Pod względem koncepcyjnym, dla każdego z pól w strukturze istnieje flaga oprócz samej struktury - lubię myśleć o Chris Morgan's cardboard box analogy. Można wyjść z pola posiadanym struktury, tak długo, jak poruszać się w przed użyciem struct:

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

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

widocznie od 2014 roku, nikt nie przeszkadza, aby umieścić w wysiłku, aby umożliwić oznaczenie całą struct jako ważny ponownie, gdy pola zostaną ponownie wypełnione.

Realistycznie nie potrzebujesz tej funkcji, ponieważ możesz po prostu przypisać całą zmienną na raz. Obecna implementacja jest zbyt bezpieczna, ponieważ Rust uniemożliwia ci zrobienie czegoś, co wydaje się być w porządku.