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

Ich versuche gerade ein Griffsystem in Rust zu erstellen und habe einige Probleme. Der obige Code ist ein einfaches Beispiel für das, was ich erreichen möchte.Wie würde ich einen Handle Manager in Rust erstellen?

Der Code funktioniert, hat aber eine Schwäche.

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 

Ich möchte einen Griff mit einem bestimmten Speicher wie diese

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

das Problem assoziieren ist, dass Rust dies nicht zulässt. Wenn ich das tun würde und ein Handle mit der Referenz eines Speichers erstellen würde, wäre es mir nicht mehr erlaubt, den Speicher zu mutieren.

Ich könnte etwas ähnliches mit einem Kanal implementieren, aber dann müsste ich T jedes Mal klonen.

Wie würde ich das in Rust ausdrücken?

+0

Ich denke, dass Sie den Marker ['ContrariariantLifetime'] (http://doc.rust-lang.org/core/kinds/marker/struct.ContravariantLifetime.html) verwenden können. – Simple

+0

@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. –

Antwort

6

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.

+1

'InvariantLifetime' wurde in der kürzlichen Überarbeitung von' BTree' verwendet, genau zum Kennzeichnen von [handles] (http://www.reddit.com/r/rust/comments/2p1mhv/slimmify_btree_by_replacing_the_three_vecs_in/), wovon ich denke, dass Sie davon wissen Haben Sie in diesem Thread kommentiert. Bedeutet es, dass es nicht vollständig sicher ist? –

+0

Ich habe diesen Trick in meinem EntityManager verwendet. Es fügt jedoch viel syntaktischen Overhead hinzu, da jede Struktur oder Funktion, die entweder HandleManager oder Handle berührt, jetzt eine Typparameter-ID haben muss. – mtsr

+0

@MatthieuM. Ich habe die Verwendung von 'InvariantLifetime' im neuen' BTree' Nodendesign nicht genau genug untersucht, um Ihnen eine definitive Antwort zu geben, aber es verdient wahrscheinlich eine gewisse Überprüfung (obwohl es ein * sehr * cooler Trick ist). – reem

Verwandte Themen