Conditional client creation in tokio_postgres

I'm trying to create the same client in two different ways: with and without SSL, according to the environment variable. Here's my code:

let ssl_mode = std::env::var("PG_SSLMODE").expect("$PG_SSLMODE is not set");
if ssl_mode == "require" {
    let mut root_cert_store = rustls::RootCertStore::empty();
    let mut pem = std::io::BufReader::new(std::io::Cursor::new(CERT_PEM));
    let certs = rustls_pemfile::certs(&mut pem).unwrap();
    for cert in certs.into_iter().map(rustls::Certificate) {
        root_cert_store.add(&cert).unwrap();
    }

    let tls_config = rustls::ClientConfig::builder()
        .with_safe_defaults()
        .with_root_certificates(root_cert_store)
        .with_no_client_auth();

    let (client, connection) =
        tokio_postgres::connect(
            &std::env::var("DB_DSN").expect("$DB_DSN is not set"),
            tokio_postgres_rustls::MakeRustlsConnect::new(tls_config)
        ).await?;
} else {
    let (client, connection) =
        tokio_postgres::connect(
            &std::env::var("DB_DSN").expect("$DB_DSN is not set"),
            tokio_postgres::NoTls
        ).await?;
}

tokio::spawn(async move {
    if let Err(e) = connection.await {
        eprintln!("connection error: {}", e);
    }
});

However, I keep getting this error:

cannot find value connection in this scope
the binding `connection` is available in a different scope in the same function

Why can't I declare a variable inside an if/else block?

You can, but it goes out of scope at the end of the block, and never gets created if the block is never entered. It no longer exists outside the block... unless you pass it out of the block.

Rust if/else chains can have values. If you want to define the client and connection conditionally, you can do something like

    let (client, connection) = if ssl_mode == "require" {
        // ...
        tokio_postgres::connect(
            &std::env::var("DB_DSN").expect("$DB_DSN is not set"),
            tokio_postgres_rustls::MakeRustlsConnect::new(tls_config)
        ).await? // <-- no semicolon 
    } else {
        tokio_postgres::connect(
            &std::env::var("DB_DSN").expect("$DB_DSN is not set"),
            tokio_postgres::NoTls
        ).await?
    };

Or you can change where the variables are declared, so far as they're given values (with consistent types) in all branches.

    let (client, connection);
    if ssl_mode == "require" {
        // ...
        (client, connection) = tokio_postgres::connect(
            &std::env::var("DB_DSN").expect("$DB_DSN is not set"),
            tokio_postgres_rustls::MakeRustlsConnect::new(tls_config)
        ).await?;
    } else {
        (client, connection) = tokio_postgres::connect(
            &std::env::var("DB_DSN").expect("$DB_DSN is not set"),
            tokio_postgres::NoTls
        ).await?;
    };

But the other version is more idiomatic when the values are at the tail of all the blocks like this.

2 Likes

Thank you, I did not realize conditional blocks had scope.

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.