Hello @jstrat, and welcome 
First of all, know that the return value of query_iter()
borrows from the Client
. Indeed, let me rewrite it renaming the parameters a bit.
impl Client {
fn query_raw<'iter, Query : ?Sized, Iterable> (
self: &'_ mut Client,
query: &'_ Query,
params: Iterable,
) -> Result<RowIter<'_>, Error>
where
Query : ToStatement,
Iterable : IntoIterator<&'iter (dyn ToSql)>,
Iterable::IntoIter : ExactSizeIterator,
-
I have also made the lifetime elisions explicit, so that we can see the "holes" where lifetime parameters live.
-
(Aside: for someone starting Rust, you have started tackling quite advanced signatures!)
Let's simplify a bit the bounds (they don't play a role in the signature itself):
impl Client {
fn query_raw_simplified<Q, I> (
self: &'_ mut Client,
query: &'_ Q,
params: I,
) -> RowIter<'_>
so, as we can see, we have a a "hole" lifetime parameter in return position, and two holes in input. The lifetime elision rules dictate that:
-
For each "hole" in input position, a distinct lifetime parameter is introduced. So here, one for self <-> client
and one for query
:
impl Client {
fn query_raw_simplified<'client, 'query, Q, I> (
self: &'client mut Client,
query: &'query Q,
params: I,
) -> RowIter<'_>
-
If there is only one "hole" in input or if there is a borrowed self
among the inputs, then that lifetime parameter (only one, or that of the self
borrow) is the one used for the lifetime parameters / "holes" in output / return position.
In this case, the elided lifetime parameter in output position is thus that of 'client
:
impl Client {
fn query_raw_simplified<'client, 'quiery, Q, I> (
self: &'client mut Client,
query: &'query Q,
params: I,
) -> RowIter<'client>
And this expresses that the lifetime parameter appearing in the return type is the one that was used for the self
input, meaning that the return value is (allowed to be) borrowing from the self
Client
.
So, now that we know that, let's see your struct
definition:
struct MyPgData<'client> {
client: Client, // <--------------------------+
iter: Option<RowIter<'client>>, // borrows: --+
}
This is thus what we call a self-referential struct, and bad news, these do not play well with Rust borrowing / ownership rules
it's one of the few limitations of the language, but you will soon see that we don't need such self-referential structs that much, at the end of the day.
So, let's refactor a bit your code to avoid them:
-
one option (the easy one) is to use APIs that don't borrow (to avoid having "infectious" lifetime parameters everywhere). Here, for instance, there is the query
function which instead of returning a lazily iterared Iterator
(RowIter
), it collects the results into a Vec
of Row
s: .query()
.
-
the other option, when the previous one is not available or is too costly, is to see that here you are constructing your object and making it borrow itself before returning. Instead, split your type into two types: the one constructed (before the borrow happens), and then a second object that borrows the first one: by having a hierarchy (no cycle as with the self-referential struct), things become much easier:
/// Before borrowing
struct MyPgData {
client: Client,
}
/// Borrowing object
struct MyPgDataQuery<'client> {
iter: Option<RowIter<'client>>,
// ...
}
impl MyPgData {
pub
fn new ()
-> Self
{
Self {
client: cfg.connect(NoTls).unwrap(),
}
}
pub
fn query_postgres (self: &'_ mut Self, query: &'_ str)
-> MyPgDataQuery<'_>
{
MyPgDataQuery {
iter: self.client.query_raw(query, iter::empty()).unwrap(),
...
}
}
}
- (in this example you can even remove the
MyPgDataQuery
wrapper middle-man, and directly return a RowIter<'_>
, it depends on how much functionality / abstraction you want to build onto the return type).
which you can then use as follows:
let mut my_pg_data = MyPgData::new();
let query = my_pg_data.query_postgres();
It may be surprising that splitting this construct magically solves the issue, but know that in Rust, having expressions be bound to actual variable(name)s does carry meaningful semantics (ownership of the expression is given to the variable (name), instead of to a temporary that may be discarded when a statement ends).