我如何在Rust中创建一个句柄管理器?

时间:2014-12-16 11:10:29

标签: rust

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
}

我目前正在尝试在Rust中创建一个句柄系统,但我遇到了一些问题。上面的代码是我想要实现的一个简单示例。

代码有效但有一个缺点。

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

我想将句柄与像这样的特定存储关联起来

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

问题是Rust不允许这样做。如果我这样做并创建一个带有存储引用的句柄,我将不再允许变更存储。

我可以用频道实现类似的功能,但每次都必须克隆T.

我如何在Rust中表达这一点?

1 个答案:

答案 0 :(得分:6)

对此进行建模的最简单方法是在Storage上使用幻像类型参数作为唯一ID,如下所示:

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
}

这可以在最简单的情况下解决您的问题,但有一些缺点。主要是,它取决于定义和使用所有这些不同的幻像类型的用途,并证明它们是独特的。它不会阻止用户可能对多个Storage实例使用相同的幻像类型的不良行为。然而,在今天的Rust中,这是我们能做的最好的事情。

另一种解决方案今天无法使用,原因我稍后会介绍,但可能会在以后工作,使用生命周期作为匿名ID类型。此代码使用InvariantLifetime标记,该标记在其使用的生命周期内删除与其他生命周期的所有子类型关系。

以下是相同的系统,重写为使用InvariantLifetime代替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);
}

在假设的未来,生命周期的分配可能会发生变化,我们可能会为这种标记提供更好的机制。但是,目前,实现此目的的最佳方法是使用幻像类型。