Rust
Connect to MotherDuck from Rust with the official duckdb crate. The bundled build compiles DuckDB from source, and md: connection strings work the same as a local database path.
MotherDuck works with the official duckdb crate (duckdb-rs). Connecting is the same as connecting to a local DuckDB database, with md: in place of a file path.
Add the dependency
[dependencies]
duckdb = { version = "1.10505.0", features = ["bundled"] }
The bundled feature compiles DuckDB from source during the build, so there's no separate DuckDB installation to manage. The crate's version numbers encode the DuckDB release it wraps rather than following DuckDB's own numbering, so check crates.io for the version matching the DuckDB release you want. See Version lifecycle for the DuckDB versions MotherDuck supports.
Connect
Set your token in the environment before running:
export motherduck_token="<motherduck_token>"
Then open a connection with md: for all your databases, or md:my_db for one:
use duckdb::{Connection, Result};
fn main() -> Result<()> {
let conn = Connection::open("md:")?;
let mut stmt = conn.prepare(
"SELECT title FROM sample_data.hn.hacker_news
WHERE title IS NOT NULL LIMIT 5",
)?;
let titles = stmt.query_map([], |row| row.get::<_, String>(0))?;
for title in titles {
println!("{}", title?);
}
Ok(())
}
You can also pass the token in the connection string, for example md:my_db?motherduck_token=<motherduck_token>. Prefer the environment variable so the token doesn't end up in logs or panic messages.
If the motherduck extension isn't autoloaded in your build, install and load it explicitly before connecting to md::
let conn = Connection::open_in_memory()?;
conn.execute_batch("INSTALL motherduck; LOAD motherduck;")?;
conn.execute_batch("ATTACH 'md:'")?;
Insert data
For bulk inserts, use the Appender API rather than a loop of INSERT statements:
conn.execute_batch("USE my_db")?;
conn.execute_batch(
"CREATE TABLE IF NOT EXISTS measurements (station INTEGER, reading INTEGER)",
)?;
let mut appender = conn.appender("measurements")?;
appender.append_rows([[1, 21], [2, 19], [3, 24]])?;
appender.flush()?;
The appender resolves the table name against the current database and schema, so set those with USE first. It buffers rows and flushes them in chunks, so flush or drop it before reading the rows back.
Things to know
- Extension loading depends on your build flags. The
bundledbuild enables extension autoloading, but a build withDUCKDB_DISABLE_EXTENSION_LOAD=1set can't load themotherduckextension at all. Ifmd:connections fail with an extension error, check the build configuration first. - One configuration per process. As with other DuckDB clients, connecting to two MotherDuck accounts with different tokens from the same process fails. See Disallowed connections with a different configuration.
- Identify your integration. If you're building a tool other people will use, pass
custom_user_agentso your traffic is identifiable in query history. See Creating a new integration.