Compiling the code gives this error:
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
In Rust, lifetimes are simply parameterized placeholders, just like generic types (see this answer for more info). That means that every returned reference must have a lifetime that corresponds to some input reference. Your function doesn't have that.
If it were possible for the lifetimes to not correspond, then you'd be able to have code that returned a lifetime that could be whatever the caller wanted it to be. This is generally nonsense, as the reference will stop being valid at some point and thus you'd be breaking the memory safety rules.
What I just said is true, but leaves off one small but important corner case: the 'static
lifetime. This is a built-in lifetime that corresponds to items compiled into the code. Normally this means global variables defined with static
or references to literal values. These values exist before main
is called and are destroyed after main
has ended. It is impossible to actually create such values during the runtime of your program.
Note that the error message makes reference to the 'static
lifetime. However, if you just add this lifetime, you will get a different error:
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 [...]
This is because the compiler cannot ensure that the value will last for the entire length of the program. In fact, it can only ensure it will last for the duration of the function call.
As the programmer, you may know (or think you know) better than the compiler. That's what the unsafe
escape hatch is for. This allows you to do things that the compiler cannot verify. It does not allow you to break memory safety; it's just up to the programmer to ensure memory safety instead of the compiler.
In your case, if you can guarantee that items from the vector are never dropped, and that you always use a Box
, then it should be safe to pretend that references to the Item
are 'static
.
A Box
ed value is allocated on the heap, and the memory is never moved after the initial creation. Since items in the vector are not dropped, the Box
will never be freed.
Here's a verbose example of implementing the method:
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
}
}
Converting the reference to a raw pointer throws away the lifetime. When we reconstitute it we can make up whatever lifetime we want.
For what it is worth, I don't think it is worth it in this case. If I actually have a global variable, I want that to be front-and-center in my code as I view it as an ugly wart. I'd much rather create a type that owned a RwLock<Vec<Box<Item>>>
, make a global of that type, then parameterize my code to accept a reference to that type. Then I lock the global when I need it and pass the reference into functions.