The easiest way to model this is to use a phantom type parameter on Storage , which acts as a unique identifier, for example:
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 }
This solves your problem in the simplest case, but has some disadvantages. Basically, it depends on the use to define and use all these different types of phantom and prove that they are unique. This does not prevent bad behavior in the user part, where they can use the same phantom type for multiple Storage instances. However, today Rust is the best we can do.
An alternative solution that does not work today for reasons that I will start later, but may work later, uses the lifetime as anonymous types of identifiers. This code uses the InvariantLifetime token, which removes all subtyping relationships with other lifetimes during its lifetime.
Here is the same system rewritten to use InvariantLifetime instead of 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 a hypothetical future, life assignment may change, and we can develop a better mechanism for this kind of tag. However, for now, the best way to accomplish this is with phantom types.