I'm trying to write a simple SQL query builder and I have an issue with the borrow checker. Here is my simplified code
fn main() {
let mut c = QueryBuilder {
query: "select ...".to_string(),
query_arguments: Some(Box::new(34)),
};
let a = AQuery {
query_string: &c.query,
};
c.query_arguments.take().unwrap().bind(a);
}
// This struct is provided by a 3rd party library. I cannot change the definition.
struct AQuery<'a> {
query_string: &'a str,
}
trait Bindable1<T> {
fn bind(self: Box<Self>, t: T) -> T;
}
impl<'a> Bindable1<AQuery<'a>> for i64 {
fn bind(self: Box<Self>, t: AQuery<'a>) -> AQuery<'a> {
// simplified
t
}
}
struct QueryBuilder<T> {
query: String,
query_arguments: Option<Box<dyn Bindable1<T>>>,
}
The compiler complains with
error[E0597]: `c.query` does not live long enough
--> src/main.rs:64:23
|
64 | query_string: &c.query,
| ^^^^^^^^ borrowed value does not live long enough
...
68 | }
| -
| |
| `c.query` dropped here while still borrowed
| borrow might be used here, when `c` is dropped and runs the destructor for type `QueryBuilder<AQuery<'_>>`
The question is how to design the QueryBuilder to contain the query and the query arguments?
Is the main function exactly as posted here? Local variables should get dropped in reverse order that they are declared, so a should get dropped before c, so that error shouldn't happen unless the declaration order is different.
Ok, I see the issue. It's rather subtle. The problem is that the type of c is going to be QueryBuilder<AQuery<'a>> for some specific lifetime 'a. Since a type can never be annotated with a smaller lifetime than the region in which the object exists, this forces that lifetime 'a to be at least the region until after c is dropped. However, then you pass a.query_string to bind, which takes an AQuery<'a>. Thus, the type of a must also be AQuery<'a> for a lifetime 'a that extends until after the destructor of c. Therefore, a is borrowing the c.query field until after the destructor of c even though a itself is dropped before c is. (There's no rule that you can't borrow stuff for longer than you exist.)
To fix this, you will need to redesign something so that QueryBuilder is not forced to pick a single specific lifetime. For example, this would work:
The above syntax requires that the type in the Box implements the following infinite list of traits:
Bindable1<AQuery<'lifetime1>>
Bindable1<AQuery<'lifetime2>>
Bindable1<AQuery<'lifetime3>>
...
Bindable1<AQuery<'static>>
The above list goes through every possible lifetime. This will work because the lifetime annotated on a is no longer also annotated on c, so it is no longer required to last until after the destructor of 'c.
I see, thank you. Is it possible to have QueryBuilder parameterized over T, i.e. QueryBuilder<T>? So that QueryBuilder is not bound to a specific implementation of Query and can be potentially used with any SQL libraries?
How would you implement it? I would like to make the QueryBuilder contain the query and the arguments that can be then late bound to some Query implementation. In my special case sqlx the Query has a lifetime Query<'q>.
Sorry, it was not quite correct in the code sample. I oversimplified the example. The actual QueryBuilder contains a vec of any type that are bindable to the query implementation. So correct would be