Confusing lifetimes for references


#1

I want to implement the trait Backend below on a custom key-value store client

pub trait Backend {
    /// pushes the data to the backend
    ///
    /// `push` should handle how the data is stored, ie: double linkedlist...etc
    fn push(&mut self, key: &[u8], data: &[u8]) -> Result<()>;

    /// gets the data associated with key provided from the backend
    ///
    /// `fetch` should handle how the data is fetched
    fn fetch(&self, key: &[u8]) -> Result<Option<&[u8]>>;
}

so firlsty I started testing by implementing Backend for HashMap collection and all was fine

impl Backend for HashMap<Vec<u8>, Vec<u8>> {
        fn push(&mut self, key: &[u8], data: &[u8]) -> Result<()> {
            self.insert(key.to_vec(), data.to_vec());
            Ok(())
        }

        fn fetch(&self, key: &[u8]) -> Result<Option<&[u8]>> {
            let data = self.get(key);
            match data {
                Some(data) => Ok(Some(&data[..])),
                None => Ok(None),
            }
        }
    }

But when I actually tried it on the client

impl Backend for Zstor {
    fn push(&mut self, key: &[u8], data: &[u8]) -> Result<()> {
        let mut req = model::WriteRequest::new();
        req.set_key(key.to_vec());
        req.set_data(data.to_vec());
        self.client
            .write(&req)
            .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
        Ok(())
    }

    fn fetch<'a>(&self, key: &[u8]) -> Result<Option<&[u8]>> {
        let mut req = model::ReadRequest::new();
        req.set_key(key.to_vec());
        let resp = self.client
            .read(&req)
            .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
         // resp.data is Vec<u8>
        Ok(Some(&resp.data))
    }
}

it gave the below error

   Compiling backends v0.1.0 (file:///Users/Khaled/development/github.com/zero-os/tlog/backends)
error[E0597]: `resp.data` does not live long enough
  --> src/zstor/mod.rs:44:18
   |
44 |         Ok(Some(&resp.data))
   |                  ^^^^^^^^^ borrowed value does not live long enough
45 |     }
   |     - borrowed value only lives until here
   |
note: borrowed value must be valid for the anonymous lifetime #1 defined on the method body at 37:5...
  --> src/zstor/mod.rs:37:5
   |
37 | /     fn fetch(&self, key: &[u8]) -> Result<Option<&[u8]>> {
38 | |         let mut req = model::ReadRequest::new();
39 | |         req.set_key(key.to_vec());
40 | |         let resp = self.client
...  |
44 | |         Ok(Some(&resp.data))
45 | |     }
   | |_____^

error: aborting due to previous error

I understand that the problem is that resp doesn’t live long enough so a reference to it does not make sense. However as far as I can see it’s the same case as the HashMap implementation.

I need help to understand this situation and how to fix it aside from changing the fetch method return to a Vec and I even tried to clone data or resp and return the ref to it but it didn’t work as expected.


#2

HashMap::get gives you a reference, and that’s the difference - your client impl must be giving you a value that dies at the end of the function.


#3

A few options to fix this.

One is to change your Backend trait like so:

pub trait Backend<'a> {
      type Fetch: AsRef<u8>;
    /// pushes the data to the backend
    ///
    /// `push` should handle how the data is stored, ie: double linkedlist...etc
    fn push(&mut self, key: &[u8], data: &[u8]) -> Result<()>;

    /// gets the data associated with key provided from the backend
    ///
    /// `fetch` should handle how the data is fetched
    fn fetch(&'a self, key: &[u8]) -> Result<Option<Fetch>>;
}

For HashMap, the Fetch type is &'a [u8] and for Zstor it’s Vec<u8>.

This approach is less than satisfactory because what’s really needed here is generic associated types so the lifetime on the associated type can be tied to &self borrow only, and not be a trait parameter.

Another approach is to do something like:

pub trait Backend {
    /// pushes the data to the backend
    ///
    /// `push` should handle how the data is stored, ie: double linkedlist...etc
    fn push(&mut self, key: &[u8], data: &[u8]) -> Result<()>;

    /// gets the data associated with key provided from the backend
    ///
    /// `fetch` should handle how the data is fetched
    fn fetch(&self, key: &[u8], mut f: F) 
where F: FnMut(Result<Option<&[u8]>>);
}

This requires caller to supply a closure to which you’ll pass a byte slice.

A third option might be to use Cow<'a, u8> instead of &[u8] with your original definition.


#4

I think this is a good example of what I meant by API design being tougher in this comment :slight_smile: