Is there a solution to this "does not live long enough" besides refactoring?

I have sqlite database with a list of files. I have a function which can iterate over these files. Because it can take a very long time to process some of the entries I want to add the ability to process a subset by specifying a (subtree) path.

So if the database contains:

foo/bar/baz/big.iso
qux/another_big_iso

.. and the user passes foo/bar to the processing function, it should query the files using a WHERE LIKE ? || '%' clause where ? is set to the foo/bar parameter. However, if the user does not pass any prefix/subset path, it should process all the files in the database.

I'm using rusqlite and am doing something like this:

// Used to gather values to bind (this will end up being empty
// if user didn't supply a subset prefix).
let mut vals: Vec<&dyn ToSql> = Vec::new();

// Determine the query string.
// sub is an Option<PathBuf> which contains
// the subset prefix if Some()
let q = if let Some(sub) = &sub {
  let sub2 = sub.to_str().unwrap(); // ToDo: Don't unwrap

  // User supplied a subset prefix path, so add it to the parameters
  // This is the problem -- rust doesn't think sub lives long enough, which is
  // true, but kind of not true as well, since it's a buffer held by the
  // Option<PathBuf>.
  vals.push(&sub2);

  // process subset query
  r#"SELECT f.id, f.ufile_id, f.fname, uf.hash
FROM files AS f
LEFT JOIN ufiles AS uf ON f.ufile_id=uf.id
WHERE f.fname LIKE ? || '%'
ORDER BY f.fname;"#
} else {
  // process all query
  r#"SELECT f.id, f.ufile_id, f.fname, uf.hash
FROM files AS f
LEFT JOIN ufiles AS uf ON f.ufile_id=uf.id
ORDER BY f.fname;"#
};

let mut stmt = conn.prepare_cached(q)?;
let mut rows = stmt.query(&vals[..])?;

while let Some(row) = rows.next()? {
  // .. process file entry ..
}

I understand what the compiler is telling me (error at the bottom), and I tried some creative ideas to work around it, but I kept running into the same problem. (I tried to return stmt, rows and vals as a tuple from the if scopes to not have to worry about references, but it would still complain about lifetimes not being long enough).

I have some workarounds which solve the issue but violate DRY far more than the solutions I tried which did not work.

Are there any solutions to this which won't require me to copy more code into the two if scopes?

Also: Should Rust be able to track the lifetime of sub back to the Option<PathBuf>? The sub: Option<PathBuf> parameter is an input parameter to the function, so it should live (at least) as long as vals.

error[E0597]: `sub2` does not live long enough
   --> src/db.rs:682:15
    |
682 |     vals.push(&sub2);
    |               ^^^^^ borrowed value does not live long enough
...
691 |   } else {
    |   - `sub2` dropped here while still borrowed
...
705 |   let mut rows = stmt.query(&vals[..])?;
    |                              ---- borrow later used here

You can declare sub2 outside of the if to extend its scope.

     // the subset prefix if Some()
+    let sub2;
     let q = if let Some(sub) = &sub {
-        let sub2 = sub.to_str().unwrap(); // ToDo: Don't unwrap
+        sub2 = sub.to_str().unwrap(); // ToDo: Don't unwrap

Playground.

1 Like

This would almost work if you didn't store an intermediate value in sub2.

        vals.push(sub.to_str().unwrap());

Error:

error[E0277]: the size for values of type `str` cannot be known at compilation time
  --> src/lib.rs:17:19
   |
17 |         vals.push(sub.to_str().unwrap());
   |                   ^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
   |
   = help: the trait `Sized` is not implemented for `str`

&str cannot coerce to &dyn Anything because

  • &str is a wide pointer (data, length). Note that length is dynamic
  • &dyn Anything is a wide pointer (data, vtable)
    • Size is stored statically in the vtable
  • So there's no way to store all of str's data, length, and vtable in a &dyn Anything

Instead you're storing &&str as &dyn ToSql (which coerces beause &str is sized). You need to keep ownership of it somewhere.

1 Like