Noob: Confused by lifetime

I have a function, inside which is a for loop, inside which is a conditional if.
Where the action from the condition is println!({}",&item), everything works fine.
Where the action from the condition is outputs.push(&item), the compiler barfs a run of complaints about lifetime:

error: cannot infer an appropriate lifetime due to conflicting requirements

So, what is the difference between these?

outputs.push(&item);
println!({}",&item);

outputs is declared earlier as

let mut outputs: Vec<&'static str> = vec!;

The error talks of the list, from which item is drawn

for item in list {

but I can't see what has changed. List is passed to the function and received as

list: &'a[&str]

The difference is that println!() borrows the item, prints it to stdout and "gives it back" to the calling code (as opposed to borrowing it).

The Vec.push() however borrwos it and does not give it back because it wants to keep it to lend it to the code that will access the Vec later.

The item is already borrowed from list so we need to give it back so the list can be iterated over again, which means we cannot give it to the Vec.

That is a very simplistic explaination but I think you get what I mean.

Also, your definition has an 'a lifetime, it would be great if you mentioned the declaration of the lifetime too.

1 Like

Actually, the following code does run:

fn main()
{
    let list=vec!["1","2","3"];
    let mut out:Vec<&'static str>=vec![];
    
    for i in list
    {
        out.push(&i);
        println!("{}",i);
    }
    
    for i in out
    {
        println!("{}",i);
    }
}

However this does not even compile:

fn foo(list:&[&str])->Vec<&'static str>
{
    let mut out:Vec<&'static str>=vec![];

    for i in list
    {  
        out.push(&i);
        println!("{}",i);
    }  

    for i in out
    {  
        println!("{}",i);
    }  

    out
}

fn main()
{
    let list=["1","2","3"];
    foo(&list);
}

Error:

$ cargo  run
Compiling rust v0.1.0 (file:///tmp/rust)
src/main.rs:5:11: 5:15 error: cannot infer an appropriate lifetime due to conflicting requirements
src/main.rs:5   for i in list
                         ^~~~
note: in expansion of for loop expansion
src/main.rs:5:2: 9:3 note: expansion site
src/main.rs:7:12: 7:14 note: first, the lifetime cannot outlive the expression at 7:11...
src/main.rs:7           out.push(&i);
                                 ^~
note: in expansion of for loop expansion
src/main.rs:5:2: 9:3 note: expansion site
src/main.rs:7:12: 7:14 note: ...so that pointer is not dereferenced outside its lifetime
src/main.rs:7           out.push(&i);
                                 ^~
note: in expansion of for loop expansion
src/main.rs:5:2: 9:3 note: expansion site
src/main.rs:5:2: 9:3 note: but, the lifetime must be valid for the expression at 5:1...
src/main.rs:5   for i in list
src/main.rs:6   {
src/main.rs:7           out.push(&i);
src/main.rs:8           println!("{}",i);
src/main.rs:9   }
note: in expansion of for loop expansion
src/main.rs:5:2: 9:3 note: expansion site
src/main.rs:5:2: 9:3 note: ...so that the type `&[&str]` will meet its required lifetime bounds
src/main.rs:5   for i in list
src/main.rs:6   {
src/main.rs:7           out.push(&i);
src/main.rs:8           println!("{}",i);
src/main.rs:9   }
note: in expansion of for loop expansion
src/main.rs:5:2: 9:3 note: expansion site
error: aborting due to previous error
Could not compile `rust`.

To learn more, run the command again with --verbose.

Explaination

This is due to the fact that list owns it's elements but out tries to borrow them without giving them back.

Is is not a problem when out goes out of scope when the function ends, because then the elements of list do exist while out lives.
If out does not go out of scope it means that out can live even when list is already "dead".
list does own it's elements which means that they do die when list dies. Therefore you had dangling references in out which, by design, are not allowed.

No; both lists "own" the &strs but shared references are Copy anyways. The first one works because the &str's in list have static lifetimes (the strings are &'static str). The second one doesn't work because the type signature of foo "hides" the fact that the &strs have static lifetimes. This is because you could call foo with a list of &strs without static lifetimes.

2 Likes

*turns back to the book*

Sorry, I sometimes think I understand Rust. Until these things happen.

1 Like

Thanks, I got it working, replacing 'static to become 'a. Not sure I understand it yet but I'll take a closer look later.

First of all, item is a &&str in your case. Borrowing that will give you a &&&str. This is certainly not what you want. What you want is

outputs.push(*item);   // #1
println!("{}",*item);  // #2

The difference between #1 and #2 is that #1 involves a conversion from &str (with some unnamed lifetime parameter) to &'static str (because of output being a Vec<&'static str>) while #2 does not. In order for this conversion to be safe and valid this unnamed lifetime has to be at least as large as 'static but you did not add any such constraint. If you want to put some of the elements of a [&str] into a Vec<&'static str> you could change the list type to be &[&'static str].

You might want to explain what actual problem you are trying to solve. It's also usually a good idea to include a short but complete example you're trying to compile.

It's hard to help you without a short but complete example and specific questions.

Thanks, use of * is interesting.

I don't yet see clearly why there's not a simple inferred default for lifetimes.. ~ everything within a function is one lifetime and the return is expected to live as long as static outside the function.

I resisted too much detail as it's an exercise from http://exercism.io/ and didn't want to spoil others fun.. test based learning works because it's not straightforward.

For the most part, in simple functions, lifetimes are completely implied. However, not exactly in the way you would expect. If the result of a function returns something with a lifetime, and the function takes in something with a lifetime, the two lifetimes are implied to be the same.

For example, this works without any lifetime specifiers:

fn convert_to_option(x: &str) -> Option<&str> {
    Some(x)
}

I think the problem you were having was with requiring an output of a function to a 'static lifetime, when the input to that function isn't guaranteed to have a 'static lifetime. 'static is a lifetime which lasts for the entire program, and is guaranteed to not ever be "dropped" (forgotten from memory), but most references are from values created on the stack, and thus will be dropped when the function they were created in ends.


@sellibitze I think you are slightly confused because of dereferencing coercions.

If you have a variable which the compiler knows is &str, and you have a str reference with any number of &s, the compiler will automatically insert enough * dereferences to get you the variable type you want.

For example, this function works perfectly fine:

fn make_a_vec(thing: &'static str) -> Vec<&'static str> {
    vec![&thing, &thing, &thing]
}

Or, to demonstrate using let statements:

fn main() {
    let a: &str = "hi"; // this is an &str
    let b = &&a; // this is an &&&str
    let c: &str = &b; // however, due to deref coercions, this is simply an `&str` again now.

    let d = &**b; // d is also an &str now. This statement is exactly equivalent to `let c: &str = &b;` in what the compiler automatically inserts.
}

As long as the type is exactly known, using & to turn an &&str into an &str works perfectly fine. I think the issue before was having the input str bound to an arbitrary lifetime, and the output str being required to be 'static.

1 Like

I don't think I am. I just wasn't aware of the (undocumented?) feature that in general coercion contexts (not only receiver coercion for method calls) references to references to T can get coerced to references T.