Use Turso Database from Rust with async/await support via the turso crate.
Turso Database is currently in BETA. It may contain bugs and unexpected behavior. Use caution with production data and ensure you have backups.
The turso crate provides a native async Rust API for Turso Database. The interface is similar to rusqlite but uses async/await throughout, backed by Tokio.
Use Builder::new_local() to open a file-based or in-memory database.
use turso::Builder;#[tokio::main]async fn main() -> turso::Result<()> { let db = Builder::new_local("my-database.db").build().await?; let conn = db.connect()?; // conn is ready to use Ok(())}
Pass ":memory:" to Builder::new_local() to create a transient in-memory database. The database is discarded when the process exits.
Connection::execute() runs a statement that does not return rows and returns the number of rows affected. Connection::query() runs a statement and returns a Rows iterator.
use turso::Builder;#[tokio::main]async fn main() -> turso::Result<()> { let db = Builder::new_local(":memory:").build().await?; let conn = db.connect()?; // Create a table conn.execute( "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)", () ).await?; // Insert rows let rows_affected = conn.execute( "INSERT INTO users (name, email) VALUES (?1, ?2)", ["Alice", "[email protected]"] ).await?; println!("Inserted {} row(s)", rows_affected); // Query rows let mut rows = conn.query("SELECT * FROM users", ()).await?; while let Some(row) = rows.next().await? { let id = row.get_value(0)?; let name = row.get_value(1)?; let email = row.get_value(2)?; println!( "id={}, name={}, email={}", id.as_integer().unwrap_or(&0), name.as_text().unwrap_or(&"".to_string()), email.as_text().unwrap_or(&"".to_string()) ); } Ok(())}
Call Connection::prepare() to prepare a statement for repeated execution. Prepared statements can be queried with Statement::query() or executed with Statement::execute().
use turso::Builder;#[tokio::main]async fn main() -> turso::Result<()> { let db = Builder::new_local(":memory:").build().await?; let conn = db.connect()?; conn.execute( "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)", () ).await?; // Prepare an insert statement let mut insert = conn.prepare( "INSERT INTO users (name, email) VALUES (?1, ?2)" ).await?; // Execute it multiple times insert.execute(["Alice", "[email protected]"]).await?; insert.execute(["Bob", "[email protected]"]).await?; // Prepare a select statement let mut select = conn.prepare( "SELECT * FROM users WHERE id = ?1" ).await?; let mut rows = select.query([1]).await?; if let Some(row) = rows.next().await? { println!("Found: {:?}", row.get_value(1)?); } Ok(())}
Iterate over rows with Rows::next(). Access column values by zero-based index using Row::get_value() which returns a Value enum, or use Row::get::<T>() to convert directly to a Rust type.
use turso::{Builder, Value};#[tokio::main]async fn main() -> turso::Result<()> { let db = Builder::new_local(":memory:").build().await?; let conn = db.connect()?; conn.execute( "CREATE TABLE users (id INTEGER, name TEXT, score REAL)", () ).await?; conn.execute( "INSERT INTO users VALUES (1, 'Alice', 98.5)", () ).await?; let mut rows = conn.query("SELECT id, name, score FROM users", ()).await?; // Inspect column metadata println!("Columns: {:?}", rows.column_names()); while let Some(row) = rows.next().await? { // Using get_value() — returns turso::Value let id = row.get_value(0)?; let name = row.get_value(1)?; // Using get::<T>() — converts to Rust type let score: f64 = row.get(2)?; match (&id, &name) { (Value::Integer(i), Value::Text(s)) => { println!("id={}, name={}, score={}", i, s, score); } _ => {} } } Ok(())}
Use Connection::transaction() to begin a transaction. It takes a &mut Connection to prevent accidental nesting at compile time. Call Transaction::commit() to commit or Transaction::rollback() to abort. Transactions roll back automatically when dropped.
use turso::Builder;#[tokio::main]async fn main() -> turso::Result<()> { let db = Builder::new_local(":memory:").build().await?; let mut conn = db.connect()?; conn.execute( "CREATE TABLE accounts (id INTEGER PRIMARY KEY, balance INTEGER)", () ).await?; conn.execute("INSERT INTO accounts VALUES (1, 1000)", ()).await?; conn.execute("INSERT INTO accounts VALUES (2, 500)", ()).await?; // Begin a transaction let tx = conn.transaction().await?; tx.execute( "UPDATE accounts SET balance = balance - 100 WHERE id = 1", () ).await?; tx.execute( "UPDATE accounts SET balance = balance + 100 WHERE id = 2", () ).await?; // Commit the transaction tx.commit().await?; println!("Transfer complete"); Ok(())}
For cases where &mut Connection is unavailable (e.g. inside Arc), use Connection::unchecked_transaction():
let tx = conn.unchecked_transaction().await?;tx.execute("INSERT INTO logs (msg) VALUES (?1)", ["event"]).await?;tx.commit().await?;
use turso::Builder;#[tokio::main]async fn main() -> turso::Result<()> { let db = Builder::new_local(":memory:").build().await?; let conn = db.connect()?; conn.execute( r#"CREATE TABLE IF NOT EXISTS posts ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, content TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP )"#, (), ) .await?; let mut insert = conn .prepare("INSERT INTO posts (title, content) VALUES (?1, ?2)") .await?; insert .execute(["Hello, Turso!", "This is the first post."]) .await?; insert .execute(["Second post", "More content here."]) .await?; let mut rows = conn .query("SELECT id, title FROM posts ORDER BY id", ()) .await?; while let Some(row) = rows.next().await? { let id: i64 = row.get(0)?; let title: String = row.get(1)?; println!("{}: {}", id, title); } Ok(())}