Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code that compiles under deadpool-postgres 0.12.1 fails to compile under 0.13.2 #323

Closed
and-reas-se opened this issue May 7, 2024 · 16 comments
Labels
A-postgres Area: PostgreSQL support / deadpool-postgres bug Category: This is a bug.

Comments

@and-reas-se
Copy link

Sorry for the non-specific title, but I don't really understand this issue. I don't have any guesses as to what's wrong. But here's the most minimal repro I could make.

use deadpool_postgres::{tokio_postgres::Row, GenericClient};
use futures_util::{Stream, StreamExt};
use postgres_types::ToSql;
use std::future::Future;

// this function borrowed from tokio_postgres source code
fn slice_iter<'a>(
    s: &'a [&'a (dyn ToSql + Sync)],
) -> impl ExactSizeIterator<Item = &'a dyn ToSql> + 'a {
    s.iter().map(|s| *s as _)
}

pub trait PgQuery {
    fn query_raw(
        db: &impl GenericClient,
        params: &[&(dyn ToSql + Sync)],
    ) -> impl Future<Output = impl Stream<Item = Row>> + Send {
        async {
            let rows = db.query_raw("SELECT 1", slice_iter(params)).await.unwrap();
            rows.map(|row| row.unwrap())
        }
    }
}

This code compiles with 0.12.1, but with 0.13.2 you get this error:

error[E0308]: mismatched types
  --> repro/src/lib.rs:18:9
   |
18 | /         async {
19 | |             let rows = db.query_raw("SELECT 1", slice_iter(params)).await.unwrap();
20 | |             rows.map(|row| row.unwrap())
21 | |         }
   | |_________^ one type is more general than the other
   |
   = note: expected reference `&dyn ToSql`
              found reference `&dyn ToSql`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `repro` (lib) due to 1 previous error
@bikeshedder
Copy link
Owner

Could you please post the output of

cargo tree -i postgres-types

It looks like you're using two versions of the postgres-types crate inside your project. Sharing your Cargo.toml file would help, too.

deadpool-postgres does re-export the tokio-postgres crate which re-exports the postgres-types crate:

use deadpool_postgres::tokio_postgres::types::ToSql;

That way you're guaranteed that you don't mix versions by accident.

It just gets a bit more tricky if you need specific features of tokio-postgres as deadpool-postgres does not re-export the features of tokio-postgres and you manually need to make sure you're using the correct version of the crates.

@bikeshedder bikeshedder added question Further information is requested A-postgres Area: PostgreSQL support / deadpool-postgres labels May 7, 2024
@and-reas-se
Copy link
Author

andreas@andreas-desktop repro $ cargo tree -i postgres-types
postgres-types v0.2.6
├── repro v0.1.0 (/home/andreas/projects/repro)
└── tokio-postgres v0.7.10
    └── deadpool-postgres v0.13.2
        └── repro v0.1.0 (/home/andreas/projects/repro)
andreas@andreas-desktop repro $

It doesn't look like that's the problem, same error message after adjusting the import. But thanks for the tip.

@bikeshedder
Copy link
Owner

Could you share the code of the repro codebase so I can check this myself? I'm stumped why two incompatible ToSql types are reported.

@and-reas-se
Copy link
Author

Cargo.toml

[package]
name = "repro"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
deadpool-postgres = "0.13.2"
futures-util = "0.3.30"

Then lib.rs as per the first post. That's the entire thing.

@bikeshedder
Copy link
Owner

After pasting the code into my own code base I was able to reproduce this issue. This almost looks like a compiler bug. Usually the error messages are more meaningful than that.

@and-reas-se
Copy link
Author

This seems to support your suspicion: rust-lang/rust#112985

Think there's any chance of a workaround, or should I downgrade for now?

@bikeshedder
Copy link
Owner

The error is completely misleading. After removing the + Send marker from the trait it does compile just fine:

 pub trait PgQuery {
     async fn query_raw(
         db: &impl GenericClient,
         params: &[&(dyn ToSql + Sync)],
-    ) -> impl Future<Output = impl Stream<Item = Row>> + Send {
+    ) -> impl Future<Output = impl Stream<Item = Row>> {
         let fut = async {
             let rows = db.query_raw("SELECT 1", slice_iter(params)).await.unwrap();
             rows.map(|row| row.unwrap())
         };
         fut
     }
 }

Now that I think about it it just makes sense. ToSql is not + Send. But if you try to return a + Send future that captures the ToSql params which are not + Send you get a type mismatch.

The compiler just forgot to tell you that it's caused by the params not being + Send.

@bikeshedder
Copy link
Owner

bikeshedder commented May 7, 2024

After copying the method signature from GenericClient::query_raw and removing the slice_iter method (which is no longer needed) I ended up with the following code which compiles just fine:

pub trait PgQuery {
    fn query_raw<P, I>(
        db: &impl GenericClient,
        params: I,
    ) -> impl Future<Output = impl Stream<Item = Row>> + Send
    where
        P: BorrowToSql,
        I: IntoIterator<Item = P> + Sync + Send,
        I::IntoIter: ExactSizeIterator,
    {
        async {
            let rows = db.query_raw("SELECT 1", params).await.unwrap();
            rows.map(|row| row.unwrap())
        }
    }
}

The signature from GenericClient::query_raw is just a copy from the original tokio-postgres crate.

The issue you were facing lies somewhere inside a missing + Send marker.

I do wonder why your code worked with the previous deadpool-postgres version. 🤔

Edit: Removed unused generic T.

@bikeshedder bikeshedder added bug Category: This is a bug. and removed question Further information is requested labels May 9, 2024
@bikeshedder
Copy link
Owner

I consider this a bug as it breaks existing code and is not a drop-in replacement for the old async_trait based version.

As far as I can tell it's actually a lifetime issue and has nothing to do with the Send marker: https://users.rust-lang.org/t/solved-one-type-is-more-general-than-the-other-but-they-are-exactly-the-same/25194/4

This is only an issue for GenericClient and not the Pool trait itself as the Pool trait doesn't contain any methods which take parameters as references.

The easiest fix at the moment would be to reintroduce async_trait for the GenericClient trait.

@and-reas-se
Copy link
Author

and-reas-se commented May 9, 2024

I agree that it's likely a lifetime issue, when messing around with my codebase I got it to produce errors like these instead, again only with the new version:

error: implementation of `Send` is not general enough
[...]
   = note: `Send` would have to be implemented for the type `&deadpool_postgres::Transaction<'_>`
   = note: ...but `Send` is actually implemented for the type `&'0 deadpool_postgres::Transaction<'_>`, for some specific lifetime `'0`

I was planning on posting an example demonstrating this, but it was more complicated to reduce to a small example, so I hadn't completed it. Seems to not be necessary now.

Edit: That's just an example, the same error appear for multiple traits and types, it's not specific to Send and Transaction.

@bikeshedder
Copy link
Owner

Thank you @conradludgate for pointing me to this page:
https://rust-lang.github.io/rfcs/3498-lifetime-capture-rules-2024.html#the-captures-trick

I'll give this a try and see if this fixes it. Otherwise I'll revert back to async_trait for GenericClient for the time being.

@conradludgate
Copy link
Contributor

Interesting, removing async, it works:

// this function borrowed from tokio_postgres source code
fn slice_iter<'a>(
    s: &'a [&'a (dyn ToSql + Sync)],
) -> impl ExactSizeIterator<Item = &'a dyn ToSql> + 'a {
    s.iter().map(|s| *s as _)
}

pub trait PgQuery {
    fn query_raw(
        db: &impl GenericClient,
        params: &[&(dyn ToSql + Sync)],
    ) -> impl Future<Output = impl Stream<Item = Row>> + Send {
        let query = db.query_raw("SELECT 1", slice_iter(params));
        query.map(|rows| rows.unwrap().map(|row| row.unwrap()))
    }
}

@bikeshedder
Copy link
Owner

Adding + Captures<...> to the return type didn't change a thing and I'm not sure if this could be a compiler error to begin with.

As I have not been able to fix this problem I'm leaning towards re-introducing async_trait for the GenericClient so updating to 0.13 doesn't break existing code.

@and-reas-se
Copy link
Author

I investigated a bit. It did not really come to anything, but I created an example that reproduces the problem, that does not use any dependencies, and has somewhat simplified signatures.

I figured I'd share it if anyone else wanted an easier starting point for experimentation.

// this function borrowed from tokio_postgres source code
fn slice_iter<'a>(
    s: &'a [&'a (dyn ToSql + Sync)],
) -> impl ExactSizeIterator<Item = &'a dyn ToSql> + 'a {
    s.iter().map(|s| *s as _)
}

pub trait PgQuery {
    fn query_raw(
        db: &impl GenericClient,
        params: &[&(dyn ToSql + Sync)],
    ) -> impl std::future::Future<Output = ()> + Send {
        async {
            db.query_raw(slice_iter(params)).await;
        }
    }
}

pub trait ToSql {}
pub trait BorrowToSql {}
impl BorrowToSql for &dyn ToSql {}

pub trait GenericClient: Sync {
    // with signature for query_raw it fails to compile
    fn query_raw<P, I>(&self, params: I) -> impl std::future::Future<Output = ()> + Send
    where
        P: BorrowToSql,
        I: IntoIterator<Item = P> + Sync + Send;

    // with this signature for query_raw it compiles
    // fn query_raw<'async_trait, P, I>(
    //     &self,
    //     params: I,
    // ) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + 'async_trait>>
    // where
    //     P: BorrowToSql,
    //     I: IntoIterator<Item = P> + Sync + Send;
}

@bikeshedder
Copy link
Owner

I reverted back to using async_trait for the GenericClient interface. I added the minimal reproduction as a test case so any future attempts to remove async_trait don't break existing code.

@bikeshedder
Copy link
Owner

I just released deadpool-postgres 0.14.0 which reverts back to using async_trait for the GenericClient trait:

I've opened a new tracking issue for the removal of async_trait in deadpool-postgres:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-postgres Area: PostgreSQL support / deadpool-postgres bug Category: This is a bug.
Projects
None yet
Development

No branches or pull requests

3 participants