How to initialize a variable with a lifetime?
Asked Answered
K

3

24

I have following code and don't know how to get it working:

fn new_int<'a>() -> &'a isize {
    &5
}

fn main() {
    let x = new_int();
}

Or another attempt:

fn new_int<'a>() -> &'a isize {
    let a: &'a isize = &5;
    a
}

fn main() {
    let x = new_int();
}
Kumasi answered 23/1, 2015 at 11:15 Comment(4)
I don't know what you want to do, but what you are doing is trying to create a reference to a value whose lifetime is smaller than 'a. It has the lifetime of the function's body.Lindbom
I'm trying to expose that variable to outside of body with lifetime parameter. If it's possible.Kumasi
that variable is dead outside of the function body. if you could access it, you would be overwriting other memory. The error message (see is.gd/ju7hFZ) tells you exactly that.Lindbom
These functions now do compile due to static promotion (see Why can I return a reference to a local literal but not a variable?Threecolor
S
33

You can't. A lifetime parameter does not allow you to choose how long a value lives, it only allows you to communicate to the compiler that two or more references are "related" to the same memory and are expected to share the same lifetime.

A function (like new_int in your case) can allocate memory in two ways:

  • locally in an area that is allocated to the function itself and is destroyed when you return from the function (the stack)
  • dynamically in an area of memory that is common to all functions (the heap)

A reference (&) is a pointer to an area of memory. It can point to the local stack, or to the heap. Since dynamic allocations are much more expensive in terms of performance than writing on the stack, Rust uses the stack by default (you have to use a Box to perform a dynamic allocation).

So, in a nutshell, this is why your code is illegal:

fn new_int<'a>() -> &'a isize {
    let a: &'a isize = &5; // write 5 on the function's local stack
    a // return a pointer to that area of memory
} // the function ends and its stack (where I wrote 5) is destroyed
  // so the pointer I'm trying to return is no longer valid

You can either return the value

fn new_int() -> isize {
    5
}

fn main() {
    let a = new_int(); // the value 5 (not a pointer) is copied into a
}

or perform a dynamic allocation (which is overkill in case of an isize but might make sense if you're actually working with a big structure)

fn new_int() -> Box<isize> {
    Box::new(5) // a Box allocates memory and writes in the heap
}

fn main() {
    let a = *new_int();
}

alternatively, you can allocate memory outside of the function and mutate it in the function. You don't typically do it for a primitive type, but it makes sense in some scenarios (e.g. streaming of data):

// new_int does not return anything. Instead it mutates
// the old_int in place
fn new_int(old_int: &mut isize) {
    *old_int = 5;
}

fn main() {
    let mut a = 2; // memory for an int is allocated locally
                   // in main()
    new_int(&mut a); // a mutable reference to that memory is passed
                     // to new_int, that overwrites it with another value
}

As @dk mentions in the comment below,, in this specific case (i.e. your function always returns 5 or some other statically known value, not something calculated dynamically by the function) you can also return a reference with a 'static lifetime:

fn new_int<'a>() -> &'a isize {
    static FIVE: isize = 5;
    &FIVE
}

You can read more about 'static in the Rust Reference.

As of Rust 1.21, this "static promotion" is now performed for you automatically and your original code compiles. It creates the equivalent of the static FIVE.

Sacci answered 23/1, 2015 at 11:44 Comment(1)
Discarding my own in-progress answer, but I want to point out a third option: if it's always returning 5, or any finite set of answers, you can return a borrowed reference to a static variable: static FIVE: isize = 5;, then &FIVE as the result of the function.Dissatisfied
G
9

An alternative way of understanding why

fn new_int<'a>() -> &'a isize {
    &5
}

can't work is as follows. 'a is a lifetime parameter of the function; that is, it is the caller who chooses the actual value of this parameter, not the function itself. For example, the caller can choose 'static lifetime:

let i: &'static isize = new_int();

However, &5 can not have the 'static lifetime so the function is rejected.

In other words, such declaration essentially says "I can give you a reference of any lifetime you want". Naturally, this is only valid if the reference returned from the function is of 'static lifetime, which is the largest lifetime possible. That's what DK. is telling about, by the way.

Gauntlett answered 23/1, 2015 at 15:41 Comment(0)
C
5

Lifetimes only describe what the code is already doing. They don't affect behavior of the code in any way.

They're not an instruction to make something live as long as required, but a consistency check that ensures the code actually does what is says it does.

In fact, Rust strips all lifetimes from the code after checking them, and then compiles the code without any knowledge of lifetimes.

Variables are destroyed at the end of their scope, and that is their lifetime. You can't say declare that they're not doing this.

Conradconrade answered 4/8, 2020 at 15:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.