How to not reference data owned by the current function?

Ich have set up a module to communicate with my database with the method syntax which should query the database and return a stream.

pub struct SqlxDatabaseConnection {
	pool: Pool<Postgres>,
}

impl SqlxDatabaseConnection {
	pub async fn establish_connection(settings: &DatabaseSettings) -> Result<Self> {
		let pool = // ...
		Ok(Self { pool })
	}

	fn convert(row: PgRow) -> Result<Value> { /* ... */ }

	pub fn query<'a>(
		&'a self,
		statement: &'a str,
	) -> Pin<Box<dyn Stream<Item = Result<Result<JsonValue>>> + 'a>> {
		Box::pin(
			sqlx::query(statement)
				.fetch(&self.pool) // ...

		)
	}
}

Now I want to use this module and return the resulting stream, so it cam be consumed somewhere else:

pub async fn query_database() -> Result<Pin<Box<dyn Stream<Item = Result<Result<Value>>>>>> {
    let connection = SqlxDatabaseConnection::establish_connection(&db_settings).await?;
    let stream = connection.query(statement);
    Ok(stream)
]

But this is what the compiler says:

error[E0515]: cannot return value referencing local variable `connection`
  --> engine/src/persistance/db.rs:30:2
   |
28 |     let stream = connection.query(statement);
   |                  ---------- `connection` is borrowed here
29 |
30 |     Ok(stream)
   |     ^^^^^^^^^^ returns a value referencing data owned by the current function

I'd like to keep the method syntax. But how can I resolve the borrowing issue?

You have two options:

A) keep database connection outside of query method and pass it in by reference

B) don't return a stream, collect it to a container and return that

2 Likes

B is not an option, since I want to consume the stream in the frontend.
I tried option A. But when I get rid of the struct and the method syntax and try to query the database like this:

	let pool = establish_connection(&db_settings).await?;
	let stream = query(&pool, statement);
	Ok(stream)

The compile gives me the same error basically:

error[E0515]: cannot return value referencing local variable `pool`
  --> engine/src/persistance/db.rs:30:2
   |
28 |     let stream = query(&pool, statement);
   |                        ----- `pool` is borrowed here
29 |
30 |     Ok(stream)
   |     ^^^^^^^^^^ returns a value referencing data owned by the current function

Method syntax is not relevant to the issue.

The issue is that you're trying to return a borrow to a value you create inside a function. You can't do that. The code that wants the result from query has to be the code to call establish_connection. You cannot abstract this without getting rid of the borrow, and you've said that isn't an option.

... okay, you can get around this by using one of the "self-referential borrowing" crates, but every time I've ever used or recommended one of those, it turned out to be unsafe and dangerous to use, so I make a point to not use or recommend them any more.

The way you solve these problems in Rust is to promote the establish_connection up the call stack until it's at the same level as (or higher than) where you need the borrowed data.

4 Likes

Actually, there is another way, but it's even weirder and only very occasionally better: change query to take conn: &mut Option<SqlxDatabaseConnection> and, if it's empty, store the result of establish_connection in there, and then return a borrow derived from that. This works because it lets you store the connection "up the stack" from inside the function.

This is less than ideal for a few reasons, though. First of all, it still breaks the abstraction of "users don't need to know about the connection" by forcing them to define and then pass an argument either way. Secondly, because it's a mutable borrow, it forces the connection to be unusable for anything else so long as the query result exists.

Just thought I should mention it so that you're aware this option exists, even if it's probably not what you want to use here.

Don't have the function own the data if you want to return a reference. So have the caller of query_database() make the call to SqlxDatabaseConnection::establish_connection() and then pass the connection down by reference:

pub async fn query_database(connection: &SqlxDatabaseConnection) -> Result<Pin<Box<dyn Stream<Item = Result<Result<Value>>> + '_>>> {
    let stream = connection.query(statement);
    Ok(stream)
}

However, you will still have to keep the connection around until after you've used the stream. Think of the current error this way: it's saying the current function owns data. Ie. the function owns the connection and will drop it at the end of the function. Dropping it means closing the connection. Thus, how will the returned stream be able to fetch the data at a later point if the connection is no longer open.

3 Likes

Thanks for your answers!! I think all of the answers suggest to "promote" the establish_connection function and pass the connection as argument to the query method.

I doubt that this is a good idea in my case. Since there will be more data sources where some of them don't support pooling, I'm actually leaning in an opposite direction, in which I only make the query function public within the SqlxDatabaseConnection implementation, hide the establish_connection from the user and handle the connection internally. not sure if that's possible but I'm currently trying it out.

with this change I now have different questions to answer, like how abstract the connection, how to automatically create a connection when there is no one open, etc. If I'll need help with that, I'll create a new topic.

However, thanks again for your answers!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.