这是一个我努力正确的最小示例。
我正在努力维护全局 Vec<Box<Item>>
, Item
的 id是它的索引。当我想获取对 Item
的引用时,我总是可以从某个地方获取它的 id,然后通过 id 获取引用(如代码中的 ref_a
)。但我更喜欢直接获取对 Item
的引用并将其传递(如 ref_b
),甚至将其保存在某个地方而不是保存 id。但我的代码不起作用。
我在get_a_certain_item()
中看到了这一点,返回值&Item
将具有与 VEC.read()
相同的生命周期因此让引用转义是无效的。然而,据我了解,由于所有Item
s 在堆中分配有框,对它的引用应该始终有效。让引用的生命周期比读取防护的生命周期长应该没有什么坏处。
如果我没有正确编写代码,我想在 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();
}
最佳答案
编译代码出现此错误:
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
}
}
将引用转换为原始指针会丢弃生命周期。当我们重构它时,我们可以构建我们想要的任何生命周期。
就其值(value)而言,我认为在这种情况下不值得。如果我确实有一个全局变量,我希望它在我的代码中位于前面和中心,因为我将其视为一个丑陋的疣。我宁愿创建一个拥有 RwLock<Vec<Box<Item>>>
的类型,创建该类型的全局变量,然后参数化我的代码以接受对该类型的引用。然后我在需要时锁定全局并将引用传递给函数。
关于static - 获取对全局向量元素的引用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34327706/