How to Connect to Snowflake from Rust

7 min readSnowflake

The first thing to know: there is no official, Snowflake-maintained Rust driver. Snowflake ships native drivers for Python, Node.js, Go, Java, .NET, and PHP, plus JDBC and ODBC -- but not Rust. Every option below is community-maintained or a wrapper around another language's driver, and that shapes the tradeoffs.

It also raises the bar for checking maintenance status before you commit. Two of the crates below are actively developed; one is coasting. This guide states which is which.

The options

CrateModelMaintenance (as of July 2026)When to use it
snowflake-connector-rsPure-Rust client over Snowflake's HTTP APIActive (1.1.0 released July 2026)Default choice for queries from Rust
snowflake-apiPure-Rust client over the undocumented driver API, Arrow resultsSlowing (last release Oct 2025)Arrow-native pipelines, PUT file uploads
adbc_snowflakeADBC wrapper around Snowflake's official Go driverActive (Apache Arrow project)Arrow-native work where you want official-driver behavior
SQL REST API + any HTTP clientNo driver at allN/A (it's an HTTP API)Minimal dependencies, simple statement execution

Before any code: two Snowflake traps

These bite in every language, and Rust error messages won't make them clearer.

The account identifier. Modern Snowflake account identifiers are <orgname>-<account_name> (dash-separated, from Admin > Accounts in Snowsight). The older locator format (xy12345.eu-central-1) still appears in legacy docs and old connection strings. If authentication fails before you even get a login error -- DNS failures, timeouts -- the identifier format is the first thing to check.

Passwords are being phased out for programmatic access. Since November 2025, Snowflake blocks single-factor password authentication for new accounts and is rolling the block out to existing ones. For unattended Rust services, key-pair (JWT) authentication is the realistic default: generate a PKCS#8 RSA key pair, register the public key with ALTER USER my_user SET RSA_PUBLIC_KEY='MIIBIjAN...', and keep the private key out of your repository.

snowflake-connector-rs: the default choice

Maintained by estie, pure Rust, async (tokio), with typed row mapping and key-pair auth enabled by default. Version 1.1.0 (as of July 2026):

[dependencies]
snowflake-connector-rs = "1.1"
tokio = { version = "1", features = ["full"] }
use snowflake_connector_rs::{
    AuthConfig, Client, ClientConfig, KeyPairConfig, SessionConfig,
};
 
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let pem = std::fs::read_to_string("/etc/secrets/rsa_key.p8")?;
    let auth = AuthConfig::key_pair(KeyPairConfig::from_pem(pem));
 
    let client = Client::new(
        ClientConfig::new("MY_USER", "MYORG-MYACCOUNT", auth).with_session(
            SessionConfig::new()
                .with_warehouse("COMPUTE_WH")
                .with_database("ANALYTICS")
                .with_schema("PUBLIC")
                .with_role("ANALYST"),
        ),
    )?;
 
    let session = client.create_session().await?;
 
    let rows = session
        .query("SELECT id, email FROM users WHERE created_at > CURRENT_DATE - 7")
        .await?
        .collect::<Vec<_>>()
        .await?;
 
    for row in &rows {
        let id: i64 = row.get("ID")?;       // note: uppercase
        let email: String = row.get("EMAIL")?;
        println!("{id} {email}");
    }
    Ok(())
}

Two things in that snippet deserve attention.

Set the warehouse, database, and schema in SessionConfig. Snowflake sessions start with no active warehouse unless one is configured; skipping this gets you No active warehouse selected in the current session on your first real query, not at connect time.

Result columns come back uppercase. Snowflake uppercases unquoted identifiers, so a column written as email in DDL is EMAIL in results. row.get("email") fails where row.get("EMAIL") works. This is Snowflake semantics, not a crate bug.

Typed rows and bind parameters

The derive feature (on by default) maps rows to structs, and statements take ? positional binds -- use them instead of formatting values into SQL:

use snowflake_connector_rs::{FromRow, Statement};
 
#[derive(Debug, FromRow)]
struct User {
    id: i64,
    email: String,
}
 
let stmt = Statement::new("SELECT id, email FROM users WHERE region = ? AND active = ?")
    .bind("emea")
    .bind(true);
 
let users = session
    .query_as::<User, _>(stmt)
    .await?
    .collect::<Vec<User>>()
    .await?;

Password auth (AuthConfig::password), OAuth tokens (AuthConfig::oauth), and browser-based SSO (AuthConfig::external_browser, behind the external-browser-sso feature) are also available -- but for services, key-pair is the one that survives the password deprecation.

snowflake-api: Arrow results and PUT support

snowflake-api (0.14.0, as of July 2026) speaks the same undocumented API the official drivers use, and returns query results as Apache Arrow record batches -- useful if your pipeline is already Arrow- or Polars-shaped. It is also the only pure-Rust option with PUT support for uploading files to stages, which the SQL REST API cannot do.

use snowflake_api::{QueryResult, SnowflakeApi};
 
let mut api = SnowflakeApi::with_password_auth(
    "MYORG-MYACCOUNT",
    Some("COMPUTE_WH"),
    Some("ANALYTICS"),
    Some("PUBLIC"),
    "MY_USER",
    Some("ANALYST"),
    "password",
)?;
 
let res = api.exec("SELECT COUNT(*) FROM users").await?;
if let QueryResult::Arrow(batches) = res {
    println!("{} batches", batches.len());
}

Certificate (key-pair) auth is supported alongside passwords. The honest caveat: releases have slowed (the last was October 2025), browser auth is unimplemented, and the crate tracks an API Snowflake doesn't document. It works today; check the repository's pulse before betting a new production system on it.

adbc_snowflake: the official Go driver, wrapped

adbc_snowflake (0.23.0, as of July 2026) is part of Apache Arrow's ADBC project. It wraps Snowflake's official Go driver, so connection behavior, auth options, and edge cases match what Snowflake actually supports -- the closest thing Rust has to an official driver.

The cost is the build: the default bundled feature compiles the Go driver from source, which requires a Go toolchain on every build machine (including CI). Alternatively, linked expects the driver library to be present at build and run time. Results are Arrow record batches, and configuration can come from environment variables:

use adbc_core::{Connection, Statement};
use adbc_snowflake::{connection, database, Driver};
 
let mut driver = Driver::try_load()?;
let mut database = database::Builder::from_env()?.build(&mut driver)?;
let mut connection = connection::Builder::from_env()?.build(&mut database)?;
 
let mut statement = connection.new_statement()?;
statement.set_sql_query("SELECT 21 + 21")?;
let mut reader = statement.execute()?;

If you're building Arrow-native data infrastructure and can tolerate the Go build dependency, this is the most future-proof path -- Apache maintains it, and Snowflake maintains the driver underneath.

The SQL REST API: no driver at all

Snowflake's documented SQL REST API executes statements over plain HTTPS. With reqwest plus the small snowflake-jwt crate (0.3.1) to generate the key-pair JWT, you get Snowflake access with no driver dependency at all. You give up Arrow results, PUT/GET, and session state, but for "run a statement, read JSON rows" it's the lightest option and immune to community-crate abandonment.

Common mistakes

MistakeSymptomFix
Locator-style account identifierConnection/DNS failures before loginUse MYORG-MYACCOUNT from Snowsight Admin > Accounts
Password auth for a service userWorks today, blocked after the 2025 rollout reaches youKey-pair auth: PKCS#8 key + ALTER USER ... SET RSA_PUBLIC_KEY
No warehouse in session configNo active warehouse selected on first querySessionConfig::new().with_warehouse(...) or ALTER USER ... SET DEFAULT_WAREHOUSE
Reading lowercase column namesrow.get("email") errorsUnquoted identifiers are uppercase: row.get("EMAIL")
Formatting values into SQL stringsInjection risk, quoting bugs? binds via Statement::bind
Picking a crate without checking its pulseStuck on an abandoned dependencyAll three crates are community projects -- check recent releases first

Once your Rust service is writing to Snowflake, you still need to look at the data. Mako connects to Snowflake alongside PostgreSQL, MySQL, and the rest, with AI-powered autocomplete for exploring warehouses you didn't design. Try it free at mako.ai.

Mako — open source

Skip the terminal. Use Mako.

Connect your database, write queries with AI assistance, and import/export data in clicks. Free to start.