2014-11-06 26 views
5

mam kłopoty ze zrozumieniem, dlaczego ten kod nie kompilacji:Lifetime problem z Send cechy

use std::cell::{Ref, RefCell}; 

struct St { 
    data: RefCell<uint> 
} 

impl St { 
    pub fn test(&self) -> Ref<uint> { 
     self.data.borrow() 
    } 
} 

// This code would compile without T constrained to be Send. 
fn func<T: Send>(_: &T) { 
} 

fn main() { 
    let s = St { data: RefCell::new(42) }; 

    { 
     let r7 = s.test(); 
     // Do not compile 
     func(&r7) 
    } 

    // Compile 
    func(&s); 
} 

To daje następujący błąd:

bug.rs:21:18: 21:19 error: `s` does not live long enough 
bug.rs:21   let r7 = s.test(); 
         ^
note: reference must be valid for the static lifetime... 
bug.rs:17:11: 28:2 note: ...but borrowed value is only valid for the block at 17:10 
bug.rs:17 fn main() { 
bug.rs:18  let s = St { data: RefCell::new(42) }; 
bug.rs:19 
bug.rs:20  { 
bug.rs:21   let r7 = s.test(); 
bug.rs:22   // Do not compile 
      ... 

Problem wydaje się być w funkcji func() kiedy próbuję ograniczyć T, aby była zgodna z cechą Send. Bez tego ograniczenia ten kod kompiluje się bezbłędnie.

Czy ktoś może mi wyjaśnić, co jest przyczyną tego zachowania?

Odpowiedz

10

Aktualizacja Rust 1,0

w Rust 1,0 a później kod w przykładzie (gdy uint s zastąpiono jakimś istniejącym typu) nie powiedzie się z innego błędu:

% rustc test.rs 
test.rs:23:9: 23:13 error: the trait `core::marker::Sync` is not implemented for the type `core::cell::UnsafeCell<usize>` [E0277] 
test.rs:23   func(&r7) 
        ^~~~ 
test.rs:23:9: 23:13 help: run `rustc --explain E0277` to see a detailed explanation 
test.rs:23:9: 23:13 note: `core::cell::UnsafeCell<usize>` cannot be shared between threads safely 
test.rs:23:9: 23:13 note: required because it appears within the type `core::cell::Cell<usize>` 
test.rs:23:9: 23:13 note: required because it appears within the type `core::cell::BorrowRef<'_>` 
test.rs:23:9: 23:13 note: required because it appears within the type `core::cell::Ref<'_, i32>` 
test.rs:23:9: 23:13 note: required by `func` 

To jest trochę podstępna - inna cecha, Sync, pojawiła się znikąd.

Typ implementujący cechę Send (choć jej dokumentacja na pewno brak jest od teraz) jest czymś, co można przenieść przez granice zadań. Większość typów to Send, ale niektóre, takie jak Rc i Weak, nie są Send, ponieważ wystąpienia takich typów mogą współdzielić niezsynchronizowany stan zmienny, a zatem nie są bezpieczne w użyciu z wielu wątków.

W starszych wersjach Rust Send domniemana 'static, więc referencje nie były Send. Jednak od rdzy 1.0 Send już nie oznacza 'static, dlatego referencje mogą być przesyłane między wątkami. Jednakże, w celu &T bycia Send, T musi być Sync: jest to wymagane przez następujący realizacji:

impl<'a, T> Send for &'a T where T: Sync + ?Sized 

Ale w naszym przypadku nie jesteśmy wymagający, że &T jest Send tylko wymagamy T jest Send, więc nie powinno to mieć znaczenia, prawda?

Nie. W rzeczywistości nadal są to odniesienia, nawet nie widzimy ich od razu. Pamiętaj, że każdy typ komponentu musi być Send, a każdy jego element musi być Send, tzn. Każde pole struktury i każda część każdego z wyliczonych wariantów wyliczenia musi być również Send dla tej struktury/enum, aby być również Send. core::cell::Ref wewnętrznie contains instancja struct BorrowRef, która z kolei contains odniesienie do Cell<BorrowFlag>. I tutaj jest, skąd pochodzi Sync: w kolejności lub &Cell<BorrowFlag> jako Send, Cell<BorrowFlag> musi być Sync; jednak nie jest i nie może być Sync, ponieważ zapewnia niezsynchronizowaną wewnętrzną zmienność. Jest to faktyczna przyczyna błędu.

+0

Świetna odpowiedź dzięki, rozumiem teraz! – user3762625

2

Według the Rust reference kopalni (nacisk):

Send : Types of this kind can be safely sent between tasks. This kind includes scalars, boxes, procs, and structural types containing only other owned types. All Send types are 'static .

Rzeczywiście, jeśli coś wysłać do innego zadania, należy zagwarantować, że nie zostaną zniszczone, zanim to inne zadanie zakończy go używać, więc nie może być własnością bieżącego zadania.

Istnieją dwa sposoby zapewnienia go:

  • Mając to obiekt jest w pełni własnością (w zasadzie wszystkie państwa od swojej struktury są wysyłane również)
  • uwzględniając Państwa obiekt będzie w statycznej pamięci

Więc wymagając, że argument o swojej funkcji, aby być Send, wymagany r7 być 'static, ale nie może przeżyć s (jak jest to odniesienie do con RefCell namioty), który nie jest zdefiniowany w twoim głównym.

Bardziej ogólnie, pisząc

fn foo<T: 'a>(bar: T); 

wymagany T być albo:

  • typ ze wszystkimi jego argumenty żywotność jest 'a lub dłużej (lub nie mając argumentów)
  • się &'a odniesienie do samego typu będącego 'a (i można rekursować na tych warunkach)

Jak widzieliśmy, T: Send implikuje T: 'static.

+0

W rzeczywistości istnieją inne sposoby zapewnienia, że ​​czas życia jest wystarczająco długi. Na przykład w trybie łączenia widłowego zamiast zapewnienia, że ​​czas życia danych jest "statyczny", tak aby każde zadanie mogło z niego bezpiecznie korzystać, zapewnia się, że czas trwania zadania nie przekracza czasu życia danych. Rust nie zapewnia jeszcze złącza widełkowego, o ile mi wiadomo. –