2014-12-16 17 views
5
pub struct Storage<T>{ 
    vec: Vec<T> 
} 
impl<T: Clone> Storage<T>{ 
    pub fn new() -> Storage<T>{ 
     Storage{vec: Vec::new()} 
    } 
    pub fn get<'r>(&'r self, h: &Handle<T>)-> &'r T{ 
     let index = h.id; 
     &self.vec[index] 
    } 
    pub fn set(&mut self, h: &Handle<T>, t: T){ 
     let index = h.id; 
     self.vec[index] = t; 
    } 
    pub fn create(&mut self, t: T) -> Handle<T>{ 
     self.vec.push(t); 
     Handle{id: self.vec.len()-1} 
    } 
} 
struct Handle<T>{ 
    id: uint 
} 

Obecnie próbuję stworzyć system uchwytów w Rust i mam pewne problemy. Powyższy kod jest prostym przykładem tego, co chcę osiągnąć.Jak utworzyć menedżera uchwytów w Rust?

Kod działa, ale ma jedną słabość.

let mut s1 = Storage<uint>::new(); 
let mut s2 = Storage<uint>::new(); 
let handle1 = s1.create(5); 
s1.get(handle1); // works 
s2.get(handle1); // unsafe 

chciałabym powiązać z konkretnym uchwyt przechowywania jak ten

//Pseudo code 
struct Handle<T>{ 
    id: uint, 
    storage: &Storage<T> 
} 
impl<T> Handle<T>{ 
    pub fn get(&self) -> &T; 
} 

Problem polega na tym, że Rust to nie pozwala. Gdybym to zrobił i utworzył uchwyt z odniesieniem do Storage, nie byłbym już w stanie mutować Storage.

Mogę zaimplementować coś podobnego z kanałem, ale wtedy będę musiał sklonować T za każdym razem.

Jak wyraziłbym to w Rust?

+0

Myślę, że możesz użyć znacznika ['ContravariantLifetime'] (http://doc.rust-lang.org/core/kinds/marker/struct.ContravariantLifetime.html). – Simple

+0

@ Prosto Myślałem o użyciu ContravariantLifetime, ale czasy życia z s1 i s2 powinny być takie same, więc myślę, że to nie zadziała, ale dam mu szansę. –

Odpowiedz

6

Najprostszym sposobem Ten model jest użyć parametru typu phantom na Storage który działa jako unikalny ID, tak jak poniżej:

use std::kinds::marker; 

pub struct Storage<Id, T> { 
    marker: marker::InvariantType<Id>, 
    vec: Vec<T> 
} 

impl<Id, T> Storage<Id, T> { 
    pub fn new() -> Storage<Id, T>{ 
     Storage { 
      marker: marker::InvariantType, 
      vec: Vec::new() 
     } 
    } 

    pub fn get<'r>(&'r self, h: &Handle<Id, T>) -> &'r T { 
     let index = h.id; 
     &self.vec[index] 
    } 

    pub fn set(&mut self, h: &Handle<Id, T>, t: T) { 
     let index = h.id; 
     self.vec[index] = t; 
    } 

    pub fn create(&mut self, t: T) -> Handle<Id, T> { 
     self.vec.push(t); 
     Handle { 
      marker: marker::InvariantLifetime, 
      id: self.vec.len() - 1 
     } 
    } 
} 

pub struct Handle<Id, T> { 
    id: uint, 
    marker: marker::InvariantType<Id> 
} 

fn main() { 
    struct A; struct B; 
    let mut s1 = Storage::<A, uint>::new(); 
    let s2 = Storage::<B, uint>::new(); 

    let handle1 = s1.create(5); 
    s1.get(&handle1); 

    s2.get(&handle1); // won't compile, since A != B 
} 

ten rozwiązuje problem w najprostszym przypadku, ale ma pewne wady. Głównie zależy to od użycia, aby zdefiniować i wykorzystać wszystkie te typy fantomów i udowodnić, że są unikatowe. Nie zapobiega złemu zachowaniu użytkownika, gdy może używać tego samego typu widmowego dla wielu instancji Storage. W dzisiejszej Rust jest to jednak najlepsze, co możemy zrobić.

Alternatywne rozwiązanie, które nie działa dzisiaj z przyczyn, które dostanę później, ale może zadziałać później, wykorzystuje okresy życia jako anonimowe typy identyfikatorów. Ten kod używa znacznika InvariantLifetime, który usuwa wszystkie relacje podpisywania z innymi cyklami życia przez całe życie.

Oto ten sam system, przepisany do korzystania InvariantLifetime zamiast InvariantType:

use std::kinds::marker; 

pub struct Storage<'id, T> { 
    marker: marker::InvariantLifetime<'id>, 
    vec: Vec<T> 
} 

impl<'id, T> Storage<'id, T> { 
    pub fn new() -> Storage<'id, T>{ 
     Storage { 
      marker: marker::InvariantLifetime, 
      vec: Vec::new() 
     } 
    } 

    pub fn get<'r>(&'r self, h: &Handle<'id, T>) -> &'r T { 
     let index = h.id; 
     &self.vec[index] 
    } 

    pub fn set(&mut self, h: &Handle<'id, T>, t: T) { 
     let index = h.id; 
     self.vec[index] = t; 
    } 

    pub fn create(&mut self, t: T) -> Handle<'id, T> { 
     self.vec.push(t); 
     Handle { 
      marker: marker::InvariantLifetime, 
      id: self.vec.len() - 1 
     } 
    } 
} 

pub struct Handle<'id, T> { 
    id: uint, 
    marker: marker::InvariantLifetime<'id> 
} 

fn main() { 
    let mut s1 = Storage::<uint>::new(); 
    let s2 = Storage::<uint>::new(); 

    let handle1 = s1.create(5); 
    s1.get(&handle1); 

    // In theory this won't compile, since the lifetime of s2 
    // is *slightly* shorter than the lifetime of s1. 
    // 
    // However, this is not how the compiler works, and as of today 
    // s2 gets the same lifetime as s1 (since they can be borrowed for the same period) 
    // and this (unfortunately) compiles without error. 
    s2.get(&handle1); 
} 

W hipotetycznej przyszłości, cesja wcieleń może się zmienić i możemy rozwijać lepszy mechanizm tego rodzaju znakowania. Jednak na razie najlepszym sposobem na osiągnięcie tego jest typ fantomowy.

+1

'InvariantLifetime' zostało użyte w ostatniej przeróbce' BTree' właśnie do tagowania [handles] (http://www.reddit.com/r/rust/comments/2p1mhv/slimmify_btree_by_replacing_the_three_vecs_in/), które, jak sądzę, jesteś świadomy skoro skomentowałeś w tym wątku. Czy to znaczy, że nie jest w pełni bezpieczny? –

+0

Użyłem tej sztuczki w moim EntityManager. Dodaje jednak dużo narzutów składniowych, ponieważ każda struktura lub funkcja, która dotyka HandleManager lub Handle, musi teraz mieć identyfikator parametru typu. – mtsr

+0

@ MatthieuM. Nie przyjrzałem się dokładnie używaniu 'InvariantLifetime' w nowym projekcie węzła' BTree', aby dać ci definitywną odpowiedź, ale prawdopodobnie zasługuje on na trochę kontroli (chociaż jest to * bardzo * fajna sztuczka). – reem