Der einfachste Weg, dies zu modellieren ist ein Phantom Typ-Parameter auf Storage
zu verwenden, die als eindeutige Kennung wirkt, etwa so:
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
}
Dieses Ihr Problem im einfachsten Fall löst, aber einige Nachteile hat. Hauptsächlich hängt es von der Verwendung ab, all diese verschiedenen Phantomtypen zu definieren und zu verwenden und zu beweisen, dass sie einzigartig sind. Es verhindert nicht schlechtes Verhalten auf dem Teil des Benutzers, wo sie den gleichen Phantom-Typ für mehrere Instanzen verwenden können. Im heutigen Rust ist dies jedoch das Beste, was wir tun können.
Eine alternative Lösung, die aus Gründen, auf die ich später eingehe, aber möglicherweise später funktioniert, nicht funktioniert, verwendet Lebenszeiten als anonyme ID-Typen. Dieser Code verwendet den Marker InvariantLifetime
, der alle Untertypenbeziehungen mit anderen Lebensdauern für die Lebensdauer entfernt, die er verwendet.
Hier ist das gleiche System, neu geschrieben InvariantLifetime
zu verwenden, statt 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 einer hypothetischen Zukunft, die Zuordnung von Leben ändern kann und wir können einen besseren Mechanismus für diese Art von Tagging wachsen. Für den Moment ist dies jedoch am besten mit Phantom-Typen möglich.
Ich denke, dass Sie den Marker ['ContrariariantLifetime'] (http://doc.rust-lang.org/core/kinds/marker/struct.ContravariantLifetime.html) verwenden können. – Simple
@Simple Ich dachte über ContravariantLifetime, aber die Lebensdauern von S1 und S2 sollten gleich sein, also denke ich, es würde nicht funktionieren, aber ich werde es versuchen. –