How to return &str lists?

 fn food_list() -> [&str; 2]
        {
            let list = ["Chicken", "Beef"];
            list
        }

I am getting an error over here,

error[E0106]: missing lifetime specifier
 --> src\main.rs:5:28
  |
5 |         fn food_list() -> [&str; 2]
  |                            ^ help: consider giving it a 'static lifetime: `&'static`
  |
  = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from

error: aborting due to previous error

For more information about this error, try `rustc --explain E0106`.
error: could not compile `learning`.

I am not too sure why my return type has to be static lifetime and I am a bit confused with its syntax to achieve static lifetime

In general, you need to specify some lifetime for the reference that is shorter (or the same) as the lifetime of the data that’s being referred to. In this case, the data you’re using is statically compiled into the program, so you can use the special lifetime 'static:

fn food_list() -> [&'static str; 2]
        {
            let list = ["Chicken", "Beef"];
            list
        }
1 Like

Hey man sorry for not quite understanding this, but when you say lifetime, do you mean like the list variable stays within memory for lifetime during runtime?

what do I need ' just before static?

The ' is the symbol used to specify that you’re typing a lifetime name, which is required by the parser; they’re one mechanism used by the compiler to ensure memory safety.

Remember that a reference is essentially a pointer to data owned by someone else. In this case, that’s the actual characters in the string. The compiler needs to know how long that data will stick around before its owner cleans it up. The 'static lifetime is a special name that means the referenced data will never go away.

3 Likes

If I were to return a String, would I need to do the same thing?

No; String owns the memory where its character data is stored, instead of referring to something else’s memory: It will remain available as long as the String exists and be deallocated when the String object is destroyed.

1 Like

@Joe232 Lifetimes are one of the most fundamental and distinctive concepts of Rust that may seem complex if you aren't familiar with them. You can refer to the Validating References with Lifetimes section of the book for a more comprehensive and easily digestible discussion.

3 Likes

One special lifetime we need to discuss is 'static , which means that this reference can live for the entire duration of the program. All string literals have the 'static lifetime, which we can annotate as follows:

So from the link that you sent me I see this. But if all string literals have the 'static lifetime, then why is my list let list = ["Chicken", "Beef"]; not 'static if all string literals have that specific lifetime?

Edit: My explanation here is a little hand-wavy and not quite right, but I think it gets the general point across. Maybe someone else can make it clearer/more precise.

It is, which is why you can return it from a function that declares its return type as 'static. Rust doesn’t ever make inferences about function signatures by looking inside the function, though: The function signature is where you tell both the compiler and other programmers how you intend the body of the function to behave.

1 Like

Oh ok I see, but then if I hover my mouse over above list in vscode, it shows [&str, 2], shouldn't it show [&'static str, 2]?

Edit: Why can I define a variable with &str, if string literals are supposed to be lifetimes how is declearing it as &str allowed then?

Rust doesn't need lifetimes annotating within the body of a function, since it has enough information to figure out what all the lifetimes are. Lifetime annotations are used on function signatures to indicate how input lifetimes map to output lifetimes, since otherwise Rust has to check the body of every function at the place where it is called and that quickly becomes unworkable. There are a few cases where lifetimes can be omitted in function signatures though, and this is called lifetime elision.

Oh sorry I poorly worded it.

I meant to say: Why can I define a variable with &str , if string literals are supposed to be 'static how is declearing it as &str allowed then?

What I said sort of still applies there. The string literal still has a 'static lifetime, it just isn't written for variables within the body of a function (or for variables in a function signature where lifetime elision is used).

1 Like

&str is an incomplete type name because the lifetime associated is omitted. When &str appears, the compiler tries to infer it from other parts of the code. For example, given

fn main() {
    let s: &str = "Hello world";
    println!("{}", s);
}

the inferred lifetime would be 'static, since the initializer is of type &'static str. Lifetime inference is similar to type inference — if you simply write let s = "Hello world";, &'static str will be deduced.

Because of the way the Rust compiler works, inter-function reasoning is not allowed. So given

fn food_list() -> [&str; 2]

the compiler is unable to infer the lifetime associated with &str, since it cannot look into the body of the function. This is why you need to help it by adding an explicit lifetime specifier:

fn food_list() -> [&'static str; 2] {
    let list = ["Chicken", "Beef"];
    list
}

fn main() {
    for food in food_list().iter() {
        println!("{}", food);
    }
}

(playground)

2 Likes

Hey man

fn main()
{

}

fn return_option(name: &str) -> Option<&str> // Why no issue over here?
{
    match name
    {
        
    }
}

Just curious to know why is there no issue if I am returning this as &str? Shouldn't it be returned as &'static str?

This is an example of the lifetime elision that @jameseb7 mentioned. Taking a reference as input to your function and then producing a reference with the same lifetime is very common, so you’re allowed to leave out the lifetimes here as a special case.

3 Likes

Typically [&str] is not something that can be returned from a function.

'static is an edge case for data compiled into the program, or leaked memory. In practice it's rare to be able to use 'static, so it's very unlikely to solve you problem beyond a "hello world" program.

Apart from the 'static edge case, &str is a temporary view into a string that has to already be permanently stored somewhere. You can't make &str out of thin air. You can't make a string in a function and then return a view into a string that lives only in a variable in a function (because the variable will be destroyed before the end of the function, so &str won't have anything to refer to any more).

So in short, you want to return owned types, like Vec<String>.

1 Like

Just to add one more bit of context, you don't need lifetimes for owned values (like String and Vec) because their memory allocation is assured to be there by the very fact that they have the type. This means that owned values imply they have a 'static lifetime, and the compiler treats it as such. This means that you can use trait bounds in generics and on trait objects to force them to always be owned (along with the Sized trait yo force them not to be bare trait objects). This is a lot of jargon that you won't need for a while, but it's good to know that there are some concepts out there and to have a vague idea what their terminology is.

2 Likes

If you’d like a more practical example of how to use lifetimes, this video by Jon Gjengset is very good: https://youtu.be/rAl-9HwD858