Temporary Values and Custom Lifetimes

Hi,

I'm trying to use the etcd crate to watch keys for changes, deserialize the new value for the key and return that through a channel:

use etcd_client::{Client, Error, WatchOptions};
use serde::Deserialize;
use serde_json;
use tokio::sync::mpsc;

pub async fn watch<'a, T>(mut client: Client, prefix: String) -> Result<mpsc::Receiver<T>, Error>
where
   T: Deserialize<'a>,
{
    let (_, mut stream) = client.watch(prefix, Some(WatchOptions::new())).await?;

    let (tx, rx) = mpsc::channel::<T>(32);

    while let Some(ref r) = stream.message().await? {
        for e in r.events() {
            match e.kv() {
                Some(kv) => {
                    let kv = kv.value();
                    let value: T = serde_json::from_slice(kv).unwrap();
                    tx.send(value).await;
                }
                None => {}
            }
        }
    }

    return Ok(rx);

But that does not compile:

error[E0716]: temporary value dropped while borrowed
  --> src/lib.rs:14:29
   |
6  | pub async fn watch<'a, T>(mut client: Client, prefix: String) -> Result<mpsc::Receiver<T>, Error>
   |                    -- lifetime `'a` defined here
...
14 |     while let Some(ref r) = stream.message().await? {
   |                             ^^^^^^^^^^^^^^^^^^^^^^^ creates a temporary which is freed while still in use
...
18 |                     let value: T = serde_json::from_slice(kv.value().clone()).unwrap();
   |                                    ------------------------------------------ argument requires that borrow lasts for `'a`
...
24 |     }
   |     - temporary value is freed at the end of this statement

Does anyone have an idea on how to make this work?

Do you really need T to be able to borrow from the source? If not, you can use T: DeserializeOwned and remove the lifetime entirely.

A hopefully accurate description of the problem:

  1. You are trying to return a value which will give out values that may be valid for at least as long as some lifetime 'a.

  2. By declaring it as a lifetime parameter, it implicitly must last as long as the function call.

  3. In the body of the function, you open the input, read from it and close it before returning, which ties the lifetime 'a to be at most as long as the input.

These conflict, as the compiler tells you: the lifetime can't be both at least as long as the function call and at most as long as local variable. In practice, this means your result can't hand out slices to the input, since it was already destroyed when the function returned.

You can easily resolve this in two ways: either drop 2, by returning values that can last an arbitrarily small lifetime - essentially meaning they don't borrow anything that isn't 'static - by using DeserializeOwned (which is an alias for for<'a> Deserialize<'a> - read as "Deserialize for any lifetime, no matter how short"); or drop 3, by pushing the lifetime problem up to the caller, and making them pass the input in, using the current 'a lifetime.

The trade-off is the first option is easier to call and use, but the Deserialize implementation of the second is allowed to hand out parts of the input, which might be more efficient.

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.