Returning values from functions, "value does not live long enough" (Serde)

The root of this question is: How do I return a value borrowed within function?

I am trying to write a helper function which gets a value from Redis, deserializes it using Serde JSON, and returns the value.

The problem is Serde's deserialization lifetime, which enforces that the data being deserialized must live as long as the resulting deserialized value.

My issue is that I retrieve the data being deserialized within the helper function. So the borrow of this data can never last longer than the lifetime of the function. However the resulting deserialized data will last longer than the function.

How do I make this helper function work? My intitution makes me think I need to do some sort of clone, copy, or move on the returned data.

Example:

use serde::Deserialize;
use serde_json;
use redis::aio::MultiplexedConnection;

/// Retreive an item represented in Redis as JSON.
async fn load_from_redis<'a, T: Deserialize<'a>>(
    redis_conn: &'a mut MultiplexedConnection,
    key: &'a str
) -> Result<T, String>
{
    let data_json: String = match redis_conn.get(key).await {
        Err(e) => return Err(format!("Failed to get data from Redis: {}", e)),
        Ok(v) => v,
    };

    match serde_json::from_str(data_json.as_str()) {
        Err(e) => Err(format!("Failed to deserialize data: {}", e)),
        Ok(v) => Ok(v),
    }
}

Which results in the error:

error[E0597]: `data_json` does not live long enough
  --> src/main.rs:49:32
   |
39 | async fn load_from_redis<'a, T: Deserialize<'a> + Copy + std::clone::Clone>(
   |                          -- lifetime `'a` defined here
...
49 |     match serde_json::from_str(data_json.as_str()) {
   |           ---------------------^^^^^^^^^----------
   |           |                    |
   |           |                    borrowed value does not live long enough
   |           argument requires that `data_json` is borrowed for `'a`
...
53 | }
   | - `data_json` dropped here while still borrowed

When you write T: Deserialize<'a>, then you are saying that the data provided to from_str lives for at least the 'a lifetime. Since the 'a lifetime is a generic parameter, it is larger than the full load_from_redis function, but the data_json variable is destroyed inside load_from_redis, so it does not live for the 'a lifetime.

You likely want to use DeserializeOwned, which says that the type T is not allowed to borrow from the provided string.

async fn load_from_redis<T: DeserializeOwned>(
    redis_conn: &mut MultiplexedConnection,
    key: &str
) -> Result<T, String>

To explain when the lifetime on the deserialize trait is useful, consider this example:

use serde::Deserialize;

fn deserialize_data<'a, T: Deserialize<'a>>(data: &'a str) -> T {
    serde_json::from_str(data).unwrap()
}

#[derive(Deserialize, Debug)]
struct Data<'a> {
    a: &'a str,
}

fn main() {
    let s = r#" { "a": "test" } "#.to_string();
    
    let data: Data = deserialize_data(&s);
    
    // drop(s);
    
    println!("{:?}", data);
}

Since Data<'a> contains a &str instead of a String, it does not actually have any ownership of the string data. It will be a reference that points directly into the original json string. If you remove the comment on drop(s);, you will see it fail to compile, because data contains a reference into s, so data must be destroyed before s is.

error[E0505]: cannot move out of `s` because it is borrowed
  --> src/main.rs:17:10
   |
15 |     let data: Data = deserialize_data(&s);
   |                                       -- borrow of `s` occurs here
16 |     
17 |     drop(s);
   |          ^ move out of `s` occurs here
18 |     
19 |     println!("{:?}", data);
   |                      ---- borrow later used here
2 Likes

Thank you for such a detailed and perfect explanation. That makes a ton of sense now. I didn't know DeserializedOwned was an option.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.