Create a reference with a specified lifetime?

I need to create a reference with an extended lifetime. Here’s the problem I’m dealing with:

struct A<'a> {
    strings: &'a mut Vec<&'a str>
}

impl<'a> A<'a> {
    fn new() -> A<'a> {
        A {
            strings: &mut Vec::new()
        }
    }
}

Playground

error[E0597]: borrowed value does not live long enough
  --> src/lib.rs:8:27
   |
8  |             strings: &mut Vec::new()
   |                           ^^^^^^^^^^ temporary value does not live long enough
9  |         }
10 |     }
   |     - temporary value only lives until here
   |
note: borrowed value must be valid for the lifetime 'a as defined on the impl at 5:6...
  --> src/lib.rs:5:6
   |
5  | impl<'a> A<'a> {
   |      ^^

From what I understand, the lifetime of the vector is too short, and I must extend it somehow, I also don’t want to store just the vector itself, as my use case is a littie bit different (but the example illustrates the problem I’m facing). The point is, is there an equivalent to &'a mut Vec::new()? AFAIK this isn’t valid syntax, as it returns a compiler error.

You can’t extend the lifetime of the Vec like that. What’s your actual use case?

2 Likes

The Vec is dropped at the end of the line, so you can’t take reference to it for outside purposes, ie take reference to it and return the reference, because at the end of its line, it is dropped and no longer exists
What you probably want is something more like so:

struct A<'a> {
    strings: &'a mut Vec<&'a str>
}

impl<'a> A<'a> {
    fn new() -> A<'a> {
        A {
            strings: Vec::new()
        }
    }
    fn add_string(&mut self, new_str: &'a str){
        self.strings.push(new_str);
    }
}

In other words, the A created by fn new now owns its strings: Vec<&'a str>

I’ve got a tuple that I have to pass as a reference to another function that accepts a reference to a tuple, the tuple is created by another function Color.as_tuple(&self). The problem is, when I call my_function(&color.as_tuple()), this error appears:

error[E0597]: borrowed value does not live long enough
   --> src/gfx.rs:274:47
    |
274 |         let tint: &'g (f32, f32, f32, f32) = &self.properties.tint.as_tuple();
    |                                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ temporary value does not live long enough
...
279 |     }
    |     - temporary value only lives until here
    |
note: borrowed value must be valid for the lifetime 'g as defined on the impl at 210:10...
   --> src/gfx.rs:210:10
    |
210 | impl<'c, 'g> GfxContext<'c, 'g> {
    |          ^^

error: aborting due to 2 previous errors

Check this file if you need to see the full source code.

I was thinking those issues were similar, and that a single fix was applicable to both. Well, turns out I was wrong :sweat_smile:

Hard to be sure without you showing more code (e.g. the new code you’re trying to compile), but why are you (a) trying to give the temporary tuple a 'g based lifetime and (b) not passing the tuple by value?

@vitalyd If I change out the Shader.send() function chain’s value arguments to a value instead of a reference, I’ll get another lifetime-related error at ShaderUniforms.add():

error[E0309]: the parameter type `T` may not live long enough
   --> src/gfx.rs:191:36
    |
189 |     pub fn add<T>(&mut self, name: &'u str, value: T)
    |                - help: consider adding an explicit lifetime bound `T: 'u`...
190 |         where T: glium::uniforms::AsUniformValue {
191 |         self.uniforms.insert(name, value.as_uniform_value());
    |                                    ^^^^^
    |
note: ...so that the type `T` is not borrowed for too long
   --> src/gfx.rs:191:36
    |
191 |         self.uniforms.insert(name, value.as_uniform_value());
    |                                    ^^^^^

error[E0309]: the parameter type `T` may not live long enough
   --> src/gfx.rs:191:42
    |
189 |     pub fn add<T>(&mut self, name: &'u str, value: T)
    |                - help: consider adding an explicit lifetime bound `T: 'u`...
190 |         where T: glium::uniforms::AsUniformValue {
191 |         self.uniforms.insert(name, value.as_uniform_value());
    |                                          ^^^^^^^^^^^^^^^^
    |
note: ...so that the reference type `&T` does not outlive the data it points at
   --> src/gfx.rs:191:42
    |
191 |         self.uniforms.insert(name, value.as_uniform_value());
    |

Which forces me to use references instead of just values.

Ok, I see:

pub trait AsUniformValue {
    fn as_uniform_value(&self) -> UniformValue;
}

UniformValue has a lifetime parameter, and it’s going to come from the borrow of self - this is annoying and perhaps indicates a misdesign in this trait, but I don’t know glium to say for sure.

How about making ShaderUniforms store a AsUniformValue trait object?

pub struct ShaderUniforms<'u> {
    uniforms: HashMap<&'u str, Box<dyn glium::uniforms::AsUniformValue + 'u>>,
}

impl<'u> ShaderUniforms<'u> {
 ...
   pub fn add<T>(&mut self, name: &'u str, value: T) 
        where T: glium::uniforms::AsUniformValue + 'u {
     self.uniforms.insert(name, Box::new(value));
   }

impl<'u> glium::uniforms::Uniforms for ShaderUniforms<'u> {
    fn visit_values<'a, F: FnMut(&str, glium::uniforms::UniformValue<'a>)>(&'a self, mut add: F) {
        for (name, value) in self.uniforms.iter() {
            // materialize the `UniformValue` here
            add(name, value.as_uniform_value());
        }
    }
}

Thank you very much! I didn’t even know Rust had that + 'lifetime syntax, maybe because I didn’t read some tutorial?

https://doc.rust-lang.org/book/second-edition/ch19-02-advanced-lifetimes.html#lifetime-bounds-on-references-to-generic-types talks about T: 'lifetime bound, albeit somewhat briefly.

That looks like a common mistake, IMHO caused by mistaking Rust references for C++ references or C pointers.

  1. Despite the name, they are not used to store or return things by reference. They are used to temporarily give access a thing that must already have a permanent storage somewhere. &mut Vec points to temporary storage on stack (C equivalent of Vec tmp = {}; return &tmp).
    Storing or returning of newly allocated objects “by reference” is done with Box or just Vec by itself is already good-enough as a pointer to data (the data in the Vec is held “by reference”).

  2. Lifetimes don’t do anything. They don’t affect generated code in any way at all. In fact, compiler removes all lifetimes after checking them, and then compiles the code without knowledge of lifetimes! Lifetimes are assertions and only describe what the code is already doing. So you can’t reference something with a specified lifetime. You can change code to reference in a way that matches what lifetimes are saying (or change lifetimes to match what the code is doing).

6 Likes