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.
Penso che tu possa usare il marcatore ['ContravariantLifetime'] (http://doc.rust-lang.org/core/kinds/marker/struct.ContravariantLifetime.html). – Simple
@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. –