Requesting help with a lifetime problem (Using redb)

I'm using redb as a storage, and I wrote a Function

pub(crate) fn query<'a, K, V, KEY, D>(table: TableDefinition<K, V>, key: KEY) -> Result<Option<D>>
where
    K: redb::RedbKey,
    V: redb::RedbValue<SelfType<'a> = &'a [u8]>,
    D: serde::de::DeserializeOwned,
    KEY: Borrow<&'a str> + std::borrow::Borrow<<K as redb::RedbValue>::SelfType<'a>>,
{
    let read: redb::ReadTransaction<'a> = DB.begin_read()?;
    let table: redb::ReadOnlyTable<'a, K, V> = read.open_table(table)?;
    let r: Option<redb::AccessGuard<'a, V>> = table.get(key)?;
    if let Some(d) = r {
        let s: D = serde_json::from_slice(d.value())?;
        Ok(Some(s))
    } else {
        Ok(None)
    }
}

Compiler didn't happy

error[E0597]: `read` does not live long enough
   --> src\db\mod.rs:212:17
    |
204 | pub(crate) fn query<'a, K, V, KEY, D>(table: TableDefinition<K, V>, key: KEY) -> Result<Option<D>>
    |                     -- lifetime `'a` defined here
...
211 |     let read = DB.begin_read()?;
    |         ---- binding `read` declared here
212 |     let table = read.open_table(table)?;
    |                 ^^^^^^^^^^^^^^^^^^^^^^ borrowed value does not live long enough
...
215 |         let s: D = serde_json::from_slice(d.value())?;
    |                                           --------- argument requires that `read` is borrowed for `'a`
...
220 | }
    | - `read` dropped here while still borrowed

This is a little bit counterintuitive.

In my opinion, the final result D didn't borrow anything

the only thing I borrowed is KEY, but didn't use it after D returned

Thanks in advance!

I think it should work with HRTBs instead of an explicit lifetime 'a:

pub(crate) fn query<K, V, KEY, D>(table: redb::TableDefinition<K, V>, key: KEY, DB: redb::Database) -> anyhow::Result<Option<D>>
where
    K: redb::RedbKey,
    for <'a> V: redb::RedbValue<SelfType<'a> = &'a [u8]>,
    D: serde::de::DeserializeOwned,
    for <'a> KEY: Borrow<&'a str> + std::borrow::Borrow<<K as redb::RedbValue>::SelfType<'a>>,
{
    let read = DB.begin_read()?;
    let table = read.open_table(table)?;
    let r = table.get(key)?;
    if let Some(d) = r {
        let s: D = serde_json::from_slice(d.value())?;
        Ok(Some(s))
    } else {
        Ok(None)
    }
}

Rustexplorer.

2 Likes

Wow, Thanks! ,learned a lesson
By the way, when to use HRTBs, when to use fn<'a>?

I'm not sure I follow. What do you mean by fn<'a>?

If you are asking why I thought that HRTBs would solve your trait bound issues, I saw that you create a reference inside your function that you try to assign to 'a. This is not possible as 'a is a generic parameter. 'a is chosen by the caller. You can't use it for local references (that don't happen to be 'a, i.e. because they reference something that you passed as an argument that binds 'a, i.e. &'a Foo or Foo<'a>). That's when you need HRTBs to express that some trait bound should be valid for any 'a (allowing you to chose 'a yourself, instead of the caller).

2 Likes

Thanks a lot for taking the time to write that clear explanation.

Can I ask one more question?

When I call query function

const TABLE: redb::TableDefinition<&str, &[u8]> = redb::TableDefinition::new("table");
pub(crate) const SETTINGS_KEY: &str = "settings";

pub(crate) async fn get() -> impl axum::response::IntoResponse {
    let r: Result<Option<Settings>> = db::query(TABLE, SETTINGS_KEY);
}

Compiler said:

error: implementation of `Borrow` is not general enough
  --> src\man\settings.rs:34:39
   |
34 |     let r: Result<Option<Settings>> = db::query(TABLE, SETTINGS_KEY);
   |                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `Borrow` is not general enough
   |
   = note: `&'2 str` must implement `Borrow<&'1 str>`, for any lifetime `'1`...
   = note: ...but it actually implements `Borrow<&'2 str>`, for some specific lifetime `'2`


I guess this is also a lifetime problem since SETTINGS_KEY has 'static lifetime, but query function needs 'a lifetime

Could you see if this works for you? I.e. leaving the HRTB on V but providing KEY with an explicit lifetime:

pub(crate) fn query<'a, K, V, KEY, D>(table: redb::TableDefinition<K, V>, key: KEY, DB: redb::Database) -> anyhow::Result<Option<D>>
where
    K: redb::RedbKey,
    for <'b> V: redb::RedbValue<SelfType<'b> = &'b [u8]>,
    D: serde::de::DeserializeOwned,
    KEY: Borrow<&'a str> + std::borrow::Borrow<<K as redb::RedbValue>::SelfType<'a>>,
{
    let read = DB.begin_read()?;
    let table = read.open_table(table)?;
    let r = table.get(key)?;
    if let Some(d) = r {
        let s: D = serde_json::from_slice(d.value())?;
        Ok(Some(s))
    } else {
        Ok(None)
    }
}

That worked! Thanks a lot!

I think I will take some time to understand these codes

Thanks for your help