获取对全局向量元素的引用

时间:2015-12-17 06:07:01

标签: static rust

这是我努力做到的最小例子。

我正在尝试维护全局Vec<Box<Item>>Item的ID是其索引。当我想获取对Item的引用时,我总是可以从某个地方获取它的id,然后通过id获取引用(在代码中为ref_a)。但我希望直接获取Item的引用并传递它(如ref_b),甚至将其保存在某处而不是保存id。但是我的代码不起作用。

我在get_a_certain_item()中看到,返回值&ItemVEC.read()具有相同的生命周期,因此让引用转义无效。但是,根据我的理解,由于所有Item都在堆中分配了框,因此对它的引用应始终有效。让参考的寿命比读保护的寿命更长是没有害处的。

如果我没有正确编写代码,我想在Rust中应该有一些惯用的方法。我将不胜感激。

// lazy_static = "0.1.15"
#[macro_use]
extern crate lazy_static;

use std::sync::RwLock;

struct Item {
    id: usize
}

lazy_static! {
    static ref VEC : RwLock<Vec<Box<Item>>> = RwLock::new(vec![
        Box::new(Item { id: 0 }), 
        Box::new(Item { id: 1 }), 
        Box::new(Item { id: 2 })]);
}

fn get_a_certain_item() -> &Item {
    & VEC.read().unwrap()[1]
}

fn get_a_certain_item_by_id() -> usize {
    1
}

fn main() {
    // this works, but verbose
    let ref_a = {& VEC.read().unwrap()[get_a_certain_item_by_id()]};

    // this doesn't work
    let ref_b = get_a_certain_item();
}

2 个答案:

答案 0 :(得分:1)

编译代码会出现此错误:

error: missing lifetime specifier [E0106]
    fn get_a_certain_item() -> &Item {
                               ^~~~~
help: run `rustc --explain E0106` to see a detailed explanation
help: this function's return type contains a borrowed value,
      but there is no value for it to be borrowed from
help: consider giving it a 'static lifetime

在Rust中,生命周期只是参数化占位符,就像泛型类型(see this answer for more info)一样。这意味着每个返回的引用必须具有与某个输入引用相对应的生命周期。你的功能没有。

如果生命周期可能无法对应,那么您就可以拥有返回生命周期的代码,这些代码可以是调用者想要的任何内容它是。这通常是无稽之谈,因为参考将在某些时候停止有效,因此您违反了内存安全规则。

我刚才所说的是正确的,但是留下了一个小而重要的角落案例:'static一生。这是一个内置生命周期,对应于编译到代码中的项目。通常,这意味着使用static定义的全局变量或对文字值的引用。这些值在调用main之前存在,并在main结束后销毁。在程序运行期间实际创建这样的值是不可能的。

请注意,错误消息引用了'static生命周期。但是,如果您只是添加此生命周期,您将收到不同的错误:

error: borrowed value does not live long enough
    &VEC.read().unwrap()[1]
     ^~~~~~~~~~~~~~~~~~~
note: reference must be valid for the static lifetime...
note: ...but borrowed value is only valid for the block at [...]

这是因为编译器不能确保该值将持续整个程序长度。实际上,它只能确保它在函数调用期间持续。

作为程序员,您可能比编译器更了解(或认为您知道)。这就是unsafe逃生舱的用途。这允许您执行编译器无法验证的操作。 允许您打破内存安全;它只是由程序员来确保内存安全而不是编译器。

在您的情况下,如果您可以保证向量中的项目从不被删除,并且始终使用Box,那么 可以安全地假装对Item的引用为'static

在堆上分配Box ed值,并且在初始创建后永远不会移动内存。由于不会删除向量中的项目,因此永远不会释放Box

以下是实施该方法的详细示例:

fn get_a_certain_item() -> &'static Item {
    // Best practice: put a paragraph explaining why this isn't
    // actually unsafe.
    unsafe {
        let as_ref: &Box<Item> = &VEC.read().unwrap()[1];
        let as_ref2: &Item = &**as_ref;
        let as_raw = as_ref2 as *const _;
        let unsafe_ref = &* as_raw;
        unsafe_ref
    }
}

将引用转换为原始指针会丢弃生命周期。当我们重新构建它时,我们可以弥补我们想要的任何生命。

对于它的价值,我认为在这种情况下它是值得的。如果我实际上有一个全局变量,我希望它在我的代码中是前沿和中心,因为我将其视为一个丑陋的疣。我更愿意创建一个拥有RwLock<Vec<Box<Item>>>的类型,创建该类型的全局,然后参数化我的代码以接受对该类型的引用。然后我在需要时锁定全局并将引用传递给函数。

答案 1 :(得分:1)

  

我可以确保在插入这些元素后我不会改变这些元素

你可以,可以吗?

但是,即使你真的可以确保向量永远不会被变异,使用类型系统仍然是一种很好的做法,使非法状态和操作不可能< / em>的

在这种情况下,您可以隐藏模块中的Vec,然后该模块的任何用户都无法改变Vec并破坏您的不变量。

#[macro_use]
extern crate lazy_static;

// Hides the gory details from the user of the API.
pub mod items {
    use std::mem::transmute;

    pub struct Item {
        pub id: usize
    }

    lazy_static! {
        static ref VEC : Vec<Item> = vec![
            Item { id: 0 },
            Item { id: 1 },
            Item { id: 2 }];
    }

    pub fn get_an_item (idx: usize) -> Option<&'static Item> {
        // As Shepmaster has pointed out, Rust is smart enough to detect
        // that the vector is immutable and allow the 'static lifetime:
        VEC.get(idx)

        // And when it isn't that smart, we can use `unsafe`
        // to tell the compiler that the 'static lifetime is okay:
        /*
        match VEC.get (idx) {
            Some (item) => {
                // `unsafe` means "safe, scout's honor", cf. http://doc.rust-lang.org/book/unsafe.html
                let item: &'static Item = unsafe {transmute (item)};
                Some (item)
            },
            None => None
        }
        */
    }
}

fn main() {
    let ref_b = items::get_an_item (1) .expect ("!1");
    assert_eq! (ref_b.id, 1);
}

请注意,由于Vec是不可变的,因此Box Item不需要items::VEC.push (items::Item {id: 3});。从数据驱动的缓存局部性角度来看,这可能会很好。

如果此模块的用户尝试使用类似VEC的代码进行未定义的行为,则会收到&#34; 错误:静态.Save()为私有< / EM>&#34;