How to deal with different variable types in function argument declaration

I am creating an application that connects to postgres, and should do that for non-encrypted and encrypted connections. It is using the r2d2 connection pool for that.

The issue that I have is that when I pass an connection to a function that performs a test, the function declaration has to specify the type of the connection.

Here I create a connection pool:

   let connection_pool = create_pool_nossl(PG_URL, threads);

Which returns the type Pool<PostgresConnectionManager<NoTls>>

I got another connection pool creator function create_pool_ssl(PG_URL, threads), which returns the type Pool<PostgresConnectionManager<MakeTlsConnector>>

I then create a connection from the pool:

    let connection = connection_pool.get().unwrap();

Which returns the type PooledConnection<PostgresConnectionManager<NoTls>>, which I am using to call a function. This way the pool remains active, and the connection is returned to the pool after the function finishes.

However, in the function declaration I need to specify the type of the connection:

fn run_truncate(mut connection: PooledConnection<PostgresConnectionManager<NoTls>>) {
    let sql_statement = "truncate table test_table";
    connection.simple_query(sql_statement).unwrap();
    println!(">> truncate table");
}

I would love to make this dynamic so this function can be handed a PooledConnection<PostgresConnectionManager<NoTls>> connection, as well as a PooledConnection<PostgresConnectionManager<MakeTlsConnector>> type?

I would make an enum with a no-tls and a tls case.

2 Likes

Hallo,

an other option would be to use traits. For example:

trait PooledConnection {
    fn simple_query(&self, query: &str) -> std::io::Result<()>;
}

struct NoTLS {}

impl PooledConnection for NoTLS {
    fn simple_query(&self, query: &str) -> std::io::Result<()> {
        //stuff
        Ok(())
    }
}

struct MakeTlsConnector {}

impl PooledConnection for MakeTlsConnector {
    fn simple_query(&self, query: &str) -> std::io::Result<()> {
        //stuff
        Ok(())
    }
}

fn run_truncate(mut connection: impl PooledConnection) {
    connection.simple_query("query").unwrap();
}

fn main() {
    let no_tls = NoTLS {};
    let tls = MakeTlsConnector {};

    run_truncate(no_tls);
    run_truncate(tls);
}
1 Like

I am trying to understand how I can make an enum and use the two types. Do you have an example?

Well, you could do this:

enum Conn {
   Tls(PooledConnection<PostgresConnectionManager<MakeTlsConnector>>),
   NoTls(PooledConnection<PostgresConnectionManager<NoTls>>),
}

Then for any methods you need to call on the connection, you can implement it for the enum, where the implementation simply matches on self and calls the method on the actual connection.

1 Like

where the implementation simply matches on self and calls the method on the actual connection.

Does that mean there is no way around doing something like @eddi0815 suggested?
Sorry to ask questions that might seem obvious to you. I come from programming shell script, awk, PLSQL, and are not familiar with impl's, which I think "the implementation simply matches on self" refers to. Can you provide an example that I could apply please?

I mean this:

impl Conn {
    fn do_something(&mut self) {
        match self {
            Conn::Tls(inner) => inner.do_something(),
            Conn::NoTls(inner) => inner.do_something(),
        }
    }
}