How to manage time to live in Rust?

How do I set up my life time correctly?

struct Device {
    client: Client,
    last_result_: String,
}

impl Device {
    pub fn get_device_info(&mut self) -> Response<GetDeviceInfo<'_>> {
        self.tmp::<GetDeviceInfo<'_>>(“get_device_info”)
    }
    pub fn tmp<'de, T: Deserialize<'de>>(&'de mut self, endpoint: &'static str) -> Response<T> {
        let req_res = ReqRes {
            device: self,
            endpoint,
        };
        req_res.get()
    }
}

struct ReqRes<'a> {
    device: &'a mut Device,
    endpoint: &'static str,
}

impl<'a> ReqRes<'a> {
    pub fn get<'de, R: Deserialize<'de>>(&'de mut self) -> Response<R> {
        self.device.last_result_ = self.post();
        serde_json::from_str(&self.device.last_result_).unwrap()
    }

    fn post(&self) -> String {
        String::new()
    }
}

#[derive(Deserialize)]
pub struct GetDeviceInfo<'a> {
    client_id: &'a str,
}

I have a mandatory struct that cannot be changed:

#[derive(Deserialize)]
pub struct GetDeviceInfo<'a> {
    client_id: &'a str,
}

There is also an endpoint get_device_info that should return this structure. Since this structure references json String, I store it in the self endpoint in the last_result_ field to have a valid lifetime.
I also have an intermediate structure to execute the query: ReqRes. That is, I need to store the lifetime of the referenced json String from the get_device_info method, through ReqRes and up to and including the get method.

Please post the full error you get from running cargo build or cargo check in the terminal.

error[E0597]: `req_res` does not live long enough
  --> src\device\test.rs:19:9
   |
14 |     pub fn tmp<'de, T: Deserialize<'de>>(&'de mut self, endpoint: &'static str) -> Response<T> {
   |                --- lifetime `'de` defined here
15 |         let mut req_res = ReqRes::<'de> {
   |             ----------- binding `req_res` declared here
...
19 |         req_res.get()
   |         ^^^^^^^------
   |         |
   |         borrowed value does not live long enough
   |         argument requires that `req_res` is borrowed for `'de`
20 |     }
   |     - `req_res` dropped here while still borrowed

GetDeviceInfo<'a> basically means that you're not allowed to load or create any new data, and the only thing you can do with it, is point it to data that has already existed before, and comes from some outer scope denoted by 'a.

Additionally, &'a mut means that 'a is strictly exclusive and completely inflexible (invariant). When combined with &'a mut self, it freezes the entire self object for the entire duration of 'a. If the scope of 'a is large (e.g. extended by being shared with a struct or trait) this often makes self impossible to use more than exactly once.

&'a mut self, and references in structs are the two most annoying difficult patterns in Rust. They're not nearly as useful as they seem, and almost always overused, mistaken for an optimization, when they're just incorrect semantics.

Unfortunately, I think all your problems stem from a design mistake of making GetDeviceInfo a temporary view type that is forbidden from storing the data. This is just not a correct type to be used for deserializing newly-loaded data into, when it needs to be retuned to a larger scope. Don't put references in structs.

https://steveklabnik.com/writing/when-should-i-use-string-vs-str/

Use DeserializeOwned, deserialize into GetDeviceInfoOwned that contains a String, and then if you really really need GetDeviceInfo<'a> somewhere, make it borrow from GetDeviceInfoOwned after that object has been returned and was stored near where the temporary view type has to be used.

1 Like

In my case I can't program the lifetime of the string to which the structure is referenced as follows:
store the string at the top level of nested structures (Device -> ReqRes - store json string in Device) and then deserialize the structure from Device? Then the lifetime should be sufficient?
That's what I'm trying to do now

If you return the newly loaded post data instead of storing it in self, and then parse it in a separate function call, you'll avoid tying &mut self to lifetime of the GetDeviceInfo.

If you must store it in self, and the data doesn't change, put it in OnceCell which will allow you to have &self method instead of &mut self, and lifetimes on &self are much more flexible.

So, as far as I can tell, I don’t have an issue with mut; the problem lies with lifetimes. I tried different ways to pass the lifetime of the Device level, and the issue was that I couldn’t get it to work. Maybe the current error does specifically concern mut, but my main task is just to pass the lifetime as I described at the beginning.

Here's the case I had initially.

Essentially, I correctly assigned the lifetimes. 'a represents the lifetime of the last_result_ string that GetDeviceInfo should refer to. 'b denotes the lifetime of the ReqRes structure, specifically the lifetime at the tmp method level.

This means that the reference to device in ReqRes should live as long as ReqRes itself, as it’s a part of it. Therefore, I set 'b: 'a (as suggested by the compiler).

However, an error still persist

use super::response::Response;
use reqwest::Client;
use serde::Deserialize;

struct Device {
    client: Client,
    last_result_: String,
}

impl Device {
    pub fn get_device_info<'a>(&'a mut self) -> Response<GetDeviceInfo<'a>> {
        self.tmp::<'a>("get_device_info")
    }
    pub fn tmp<'a, T: Deserialize<'a>>(&'a mut self, endpoint: &'static str) -> Response<T> {
        let mut req_res = ReqRes::<'a> { // strart lifetime 'b
            device: self,
            endpoint,
        };
        req_res.get()
    }
}

struct ReqRes<'a> {
    device: &'a mut Device,
    endpoint: &'static str,
}

impl<'a> ReqRes<'a> {
    pub fn get<'b, R: Deserialize<'a>>(&'b mut self) -> Response<R>
    where
        'b: 'a
    {
        self.device.last_result_ = self.post();
        serde_json::from_str(&self.device.last_result_).unwrap()
    }

    fn post(&self) -> String {
        String::new()
    }
}

#[derive(Deserialize)]
pub struct GetDeviceInfo<'a> {
    client_id: &'a str,
}
error[E0597]: `req_res` does not live long enough
  --> src\device\test.rs:19:9
   |
14 |     pub fn tmp<'a, T: Deserialize<'a>>(&'a mut self, endpoint: &'static str) -> Response<T> {
   |                -- lifetime `'a` defined here
15 |         let mut req_res = ReqRes::<'a> { // strart lifetime 'b
   |             ----------- binding `req_res` declared here
...
19 |         req_res.get()
   |         ^^^^^^^------
   |         |
   |         borrowed value does not live long enough
   |         argument requires that `req_res` is borrowed for `'a`
20 |     }
   |     - `req_res` dropped here while still borrowed

This is not correct. &'a mut T is covariant in 'a (just like for a shared reference) but invariant in T. &'a mut Foo<'a> is invariant in 'a because 'a appears in the referent type, not because 'a is the reference lifetime.

What this distinction means is that it’s fine to reuse the same lifetime for & and &mut references, e.g. fn do_something<'a>(x: &'a Foo, y: &'a mut Bar); what’s important is not using that same lifetime inside of the types of things pointed to by &mut references.

7 Likes

This looks like a similar problem to creating an iterator over &mut _ items:

struct ReqRes<'a> {
    device: &'a mut Device,
    endpoint: &'static str,
}

impl<'a> ReqRes<'a> {
    pub fn get<'b, R: Deserialize<'a>>(&'b mut self) -> Response<R>

You can't return an R that borrows 'a because you can only reborrow self.device for 'b, the shorter lifetime. There's no way to split or replace the borrow of &'a mut Device presumably, so you need to give up ownership instead.

    //                             vvvv
    pub fn get<R: Deserialize<'a>>(self) -> Response<R> {
        self.device.last_result_ = self.post();
        serde_json::from_str(&self.device.last_result_).unwrap()
    }

With that taking ownership, you may find the ability to reborrow ReqRes to be useful elsewhere.

    pub fn reborrow(&mut self) -> ReqRes<'_> {
        ReqRes {
            device: self.device,
            endpoint: self.endpoint,
        }
    }
2 Likes

Wow, I didn't even know the details. So in the get method I get something like &'short &'long mut T and when I try to create ReqRes with 'a in tmp and create its invariant in req_res.get() it acts only as 'b which is too short for 'a` and can't be shortened or extended

Close to that. You can reborrow at most a &'short mut T through a &'short mut &'long mut T. (The &'short mut T can then be further shortened, but not extended. And further shortened doesn't help you here.)

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.