1

I am trying to have a deeper understanding of how rust works. I am trying to do some serializing and deserializing to save and load a struct with a generic type. I got it to work, but I don't understand the HRTB and why they made the code work. Initially I have this

use serde::Deserialize;
use bincode;
use std::fs;

#[derive(Deserialize)]
pub struct Construct<T> {
    data: Vec<T>
}

impl <'a, T: Deserialize<'a>> Construct<T> {
    pub fn load() -> Self {
        match fs::read("data.sav") {
            Ok(d) => {
                let c: Construct<T>  = bincode::deserialize(&d).unwrap();
                c
            },
            Err(e) => {
                println!("{e}, passing empty Construct");
                Self { data: Vec::new() }
            }
        }
    }
}

whihc produces this error

error[E0597]: `d` does not live long enough
  --> src/main.rs:14:49
   |
10 | impl <'a, T: Deserialize<'a>> Construct<T> {
   |       -- lifetime `'a` defined here
...
14 |                 let c: Construct<T>  = bincode::deserialize(&d).unwrap();
   |                                        ---------------------^^-
   |                                        |                    |
   |                                        |                    borrowed value does not live long enough
   |                                        argument requires that `d` is borrowed for `'a`
15 |                 c
16 |             },
   |             - `d` dropped here while still borrowed

I have fixed the impl block to take a higher ranked trait bound. And it works just fine.

...
impl <T: for<'a> Deserialize<'a>> Construct<T> {
    pub fn load() -> Self {

...

As I understand it Deserialize needs to make sure that the input reference lives as long as the out structure(https://serde.rs/lifetimes.html), and the difference between declaring the trait in the first example and using for<'a>. Is that the 1st example the lifetime is being provided by the caller and the for<'a> is getting the lifetime from the impl itself. (How does "for<>" syntax differ from a regular lifetime bound?) Am I right in thinking that with the for<'a> syntax we are getting the lifetime from the implementation block and that gives us a longer lifetime than from calling the function? Is there another way to code this load function without using HRTBs?

1 Answers1

0

Am I right in thinking that with the for<'a> syntax we are getting the lifetime from the implementation block

Yes, from the call bincode::deserialize(&d). Specifically, the lifetime of d.

and that gives us a longer lifetime than from calling the function

Nope, a shorter: instead of a caller-decided lifetime, that will always be longer than d's lifetime (because it is declared inside our function), we get a lifetime for only d.

Is there another way to code this load function without using HRTBs?

Yes, by bounding T to DeserializeOwned. But this just hides the HRTB: DeserializeOwned uses them behind the scene.

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
  • Thank you for the response, so if HRTB gives us a shorter lifetime when it is used on the impl block, why then is that more correct for deserialization, where it seems we would want a longer lifetime if possible. – fudgeBreadstein Dec 06 '22 at 15:40
  • @fudgeBreadstein We want to be able to deserialize from the lifetime of `d` that is owned by this function. The rest does not matter. It probably means that the deserialized data does not use the lifetime as otherwise it cannot stay valid for any lifetime. – Chayim Friedman Dec 06 '22 at 15:46