How to Connect to Snowflake from Rust
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
| Crate | Model | Maintenance (as of July 2026) | When to use it |
|---|---|---|---|
snowflake-connector-rs | Pure-Rust client over Snowflake's HTTP API | Active (1.1.0 released July 2026) | Default choice for queries from Rust |
snowflake-api | Pure-Rust client over the undocumented driver API, Arrow results | Slowing (last release Oct 2025) | Arrow-native pipelines, PUT file uploads |
adbc_snowflake | ADBC wrapper around Snowflake's official Go driver | Active (Apache Arrow project) | Arrow-native work where you want official-driver behavior |
| SQL REST API + any HTTP client | No driver at all | N/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
| Mistake | Symptom | Fix |
|---|---|---|
| Locator-style account identifier | Connection/DNS failures before login | Use MYORG-MYACCOUNT from Snowsight Admin > Accounts |
| Password auth for a service user | Works today, blocked after the 2025 rollout reaches you | Key-pair auth: PKCS#8 key + ALTER USER ... SET RSA_PUBLIC_KEY |
| No warehouse in session config | No active warehouse selected on first query | SessionConfig::new().with_warehouse(...) or ALTER USER ... SET DEFAULT_WAREHOUSE |
| Reading lowercase column names | row.get("email") errors | Unquoted identifiers are uppercase: row.get("EMAIL") |
| Formatting values into SQL strings | Injection risk, quoting bugs | ? binds via Statement::bind |
| Picking a crate without checking its pulse | Stuck on an abandoned dependency | All 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.
Skip the terminal. Use Mako.
Connect your database, write queries with AI assistance, and import/export data in clicks. Free to start.