2014-12-16 9 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 
} 

Attualmente sto cercando di creare un sistema di handle in Rust e ho alcuni problemi. Il codice sopra è un semplice esempio di ciò che voglio raggiungere.Come dovrei creare un gestore di handle in Rust?

Il codice funziona ma presenta un punto debole.

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 

vorrei associare un manico con un deposito specifico come questo

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

Il problema è che la ruggine non consente questo. Se lo facessi e creerei un handle con il riferimento di uno Storage, non mi sarebbe più permesso di mutare lo Storage.

Potrei implementare qualcosa di simile con un canale ma poi avrei dovuto clonare T ogni volta.

Come esprimerlo in Rust?

+0

Penso che tu possa usare il marcatore ['ContravariantLifetime'] (http://doc.rust-lang.org/core/kinds/marker/struct.ContravariantLifetime.html). – Simple

+0

@Simple Ho pensato di usare ContravariantLifetime ma le durate da s1 e s2 dovrebbero essere le stesse, quindi penso che non funzionerebbe, ma ci provo. –

risposta

6

Il modo più semplice per modellare questo è quello di utilizzare un parametro di tipo phantom Storage che agisce come un ID univoco, in questo modo:

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 
} 

questo risolve il problema nel caso più semplice, ma ha alcuni aspetti negativi. Principalmente, dipende dall'uso per definire e utilizzare tutti questi diversi tipi di fantasma e dimostrare che sono unici. Non impedisce un cattivo comportamento da parte dell'utente in cui possono utilizzare lo stesso tipo di fantasma per più istanze di Storage. Nell'odierna Ruggine, tuttavia, questo è il meglio che possiamo fare.

Una soluzione alternativa che non funziona oggi per motivi a cui ci arriverò più avanti, ma che potrebbe funzionare in un secondo momento, utilizza le durate come tipi di ID anonimi. Questo codice utilizza il marcatore InvariantLifetime, che rimuove tutte le relazioni di sottotitolazione con altre durate per tutta la vita che utilizza.

Qui è lo stesso sistema, riscritto per usare InvariantLifetime invece di 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); 
} 

In un ipotetico futuro, l'assegnazione dei tempi di vita può cambiare e ci può far crescere un meccanismo migliore per questo tipo di tagging. Tuttavia, per ora, il modo migliore per farlo è con i tipi di fantasma.

+1

'InvariantLifetime' è stato utilizzato nella rilavorazione recente di' BTree' proprio per il tagging [handle] (http://www.reddit.com/r/rust/comments/2p1mhv/slimmify_btree_by_replacing_the_three_vecs_in/), che immagino tu sappia dato che hai commentato in questa discussione. Significa forse che non è completamente sicuro? –

+0

Ho usato questo trucco nel mio EntityManager. Aggiunge un sacco di overhead sintattico, tuttavia, poiché ogni struct o funzione che tocca HandleManager o Handle deve ora avere un Id del parametro di tipo. – mtsr

+0

@MatthieuM. Non ho guardato abbastanza da vicino l'uso di 'InvariantLifetime' nel nuovo design del nodo' BTree' per darti una risposta definitiva, ma probabilmente merita un esame accurato (anche se è un * trucco molto * figo). – reem

Problemi correlati