rusqlite is the SQLite binding Rust projects actually ship
Ergonomic bindings to SQLite for Rust
At a glance
- What is it?
- MIT licensed, edition 2024, Rust 1.88 minimum, 168 open issues. A synchronous wrapper whose README is unusually honest about why bundled SQLite is not the default, and which has now fixed a SQL injection in SAVEPOINT names.
- Who is it for?
- rusqlite is the choice when a Rust project needs SQLite with real control over the API, including the unglamorous parts: virtual tables, blob I/O, tracing hooks and SQLCipher. What you give up relative to an async client is direct concurrency, and what you get is an API surface that maps onto SQLite itself rather than onto a framework.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 5 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Star history
- rusqlite/rusqlite4,445 ★ · +14
| Repository | 2026-10-07 | 2026-10-23 |
|---|---|---|
| rusqlite/rusqlite | 4.4k | 4.4k |
14 Stars in the last 16 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
A synchronous wrapper with a deliberate lineage
The README calls rusqlite an ergonomic wrapper for using SQLite from Rust, and the GitHub description shortens that to ergonomic bindings to SQLite for Rust. Both are accurate and both undersell one design decision: this is a synchronous library. There is no executor inside, no connection pool, no async surface. If your Rust program is already built around a runtime, rusqlite runs blocking calls and you decide where they belong.
The lineage note is unusual and useful. The API was historically based on the one from `rust-postgres`, but the README states plainly that the two have diverged in many ways and that no compatibility between them is intended. So if you are porting from `postgres`, the parameter binding patterns will look familiar while everything else should be read from docs.rs rather than assumed.
The repository is MIT licensed, written in Rust, and reports 4,405 stars with 168 open issues. The last push was on 2026-09-21 and the project is not archived. Cargo metadata shows edition 2024 with `rust-version = 1.88.0`, keywords `sqlite`, `database` and `ffi`, and a crates.io maintenance badge of `actively-developed`.
The workspace layout is worth noting because it explains the two-crate design: `libsqlite3-sys/` is a workspace member and holds the FFI layer, `rusqlite-macros/` for derive support, `src/` for the wrapper itself, plus `benches/`, `tests/`, `examples/`, `bindings.md`, `types.md` and `Changelog.md`. A bindings crate separate from the safe wrapper is what lets the same crate work whether SQLite comes from the system, from bundled sources, or from SQLCipher.
Why bundled SQLite is opt-in and not the default
This is the most instructive paragraph in the README, because it explains a choice most wrappers make silently. The suggested Cargo snippet carries a long comment justifying the feature:
rusqlite = { version = "0.40.1", features = ["bundled"] }The comment says that `bundled` causes rusqlite to automatically compile and link in an up to date version of SQLite, which avoids many common build issues and avoids depending on the version of SQLite on the user's system, which may be old or missing. It is called the right choice for most programs that control their own SQLite databases.
Then it gives the counter-case: it is not ideal for all scenarios, and generic libraries built around `rusqlite` should probably not enable it, which is why it is not a default feature, since it could become hard to disable. That is a rare and valuable admission. A library author enabling `bundled` inside their own crate would impose a SQLite version on every downstream user and quietly override the platform's, which is exactly the kind of transitive decision that causes painful debugging.
Cargo.toml confirms the mechanics: `bundled = ["libsqlite3-sys?/bundled", "modern_sqlite"]`, so enabling it pulls the bundled sources through the FFI crate and also implies the `modern_sqlite` marker. For Windows users the feature list recommends `bundled` explicitly as a good option where linking to SQLite is complicated. If you want encryption instead, the chain runs the other way: `sqlcipher` links against the SQLCipher library and overrides `bundled`, `bundled-sqlcipher` bundles SQLCipher while searching for a system crypto library, and `bundled-sqlcipher-vendored-openssl` adds a vendored OpenSSL via `openssl-sys`.
The example is short enough to learn the whole API from
The README's usage example is the best documentation in the file, because it shows the full arc of the API without ceremony. It opens with a plain struct and an in-memory connection:
use rusqlite::{Connection, Result};
#[derive(Debug)]
struct Person {
id: i32,
name: String,
data: Option<Vec<u8>>,
}
fn main() -> Result<()> {
let conn = Connection::open_in_memory()?;Then it creates the table with a parameterless `execute`, inserts a row with positional parameters, and reads the rows back through a prepared statement:
let mut stmt = conn.prepare("SELECT id, name, data FROM person")?;
let person_iter = stmt.query_map([], |row| {
Ok(Person {
id: row.get(0)?,
name: row.get(1)?,
data: row.get(2)?,
})
})?;
for person in person_iter {
println!("Found person {:?}", person.unwrap());
}Four things are visible there. The `Result` alias keeps error handling in the function signature, so a real project will return `rusqlite::Result` from most functions. Positional parameters use `?1` and `?2` with a tuple of references, and the DDL call passes `()` with an inline comment noting it is an empty list of parameters, which is the convention for parameterless statements. Row extraction is positional by index rather than by column name, which is fast and means column order matters.
One caveat is in the README rather than the example: the base package supports SQLite version 3.45.3 or newer, and older versions require filing an issue. Some cargo features need a newer SQLite than that, so if you link against a system library you should check the feature list against your runtime version.
Feature flags are the real API surface
The optional features list is long and worth reading as documentation, because each flag maps to a specific SQLite capability that would otherwise be unavailable. `load_extension` allows loading dynamic library-based SQLite extensions, and `loadable_extension` goes the other way, letting you program a loadable extension in Rust. `backup` exposes SQLite's online backup API. `functions` loads Rust closures into a connection for use in queries, and `window` adds window function support and implies `functions`.
Then there is instrumentation and data access. `trace` hooks into SQLite's tracing and profiling APIs, `blob` gives `std::io::{Read, Write, Seek}` access to SQL BLOBs, and `limits` lets you set and retrieve per-connection limits. `hooks` provides commit, rollback and data change notification callbacks, `preupdate_hook` adds preupdate notifications and implies `hooks`, and `unlock_notify` covers the unlock notification API.
The interop flags are the reason to choose this crate specifically. `serde_json`, `chrono`, `time`, `jiff` and `url` each implement `FromSql` and `ToSql` for their types, so a column can decode straight into a `DateTime` or a `Url` without a manual conversion layer. `vtab` enables virtual table support for writing virtual table implementations in Rust, with the current limitation that only read-only virtual tables are supported. `collation` and `cache` appear in Cargo.toml as well, the latter providing a prepared statement cache per connection.
The `cache` flag deserves a note because it changes behaviour rather than adding capability: `cache = ["hashlink"]` uses a prepared statement cache per connection, which is the same idea as SQLite's own Tcl cache.
0.40 rewrote the virtual table API and fixed a real injection
Version 0.40.0, published 2026-05-26, is a breaking release dominated by virtual table work. The changelog lists replacing the `VTab` macros with constructors, fixing `VTab::best_index`, fixing `VTab::connect` and `create`, asserting on `VTab::connect` auxiliary data and arguments, and fixing `vtab::dequote`. If you used the macro-based virtual table API, that surface changed shape, and this is the release to read closely rather than skim.
Two other entries stand out. `Fix UB in ToSqlOutput::from_rc` is a soundness fix, and the follow-up note that miri no longer complains suggests it was verified under that tool. `Allow opting out of using sqlite-wasm-rs on wasm32-unknown-unknown` is a portability escape hatch for wasm builds, with the bundled SQLite bumped to 3.53.1 in the same release and SQLCipher to 4.14.0.
Version 0.40.1, published 2026-06-06, is the one to note for security reasons: `Fix SQL injection when SAVEPOINT name is tainted`. Tainted here means a savepoint name derived from runtime data, so code building savepoint names from user input or from a multi-tenant key was exposed. The same release fixed clippy warnings and bumped the bundled SQLite to 3.53.2.
Version 0.40.2, published 2026-08-08, contains exactly one change: lower the MSRV to 1.88.0. That is a meaningful release for anyone held back by a toolchain pin, and it is also why the newest release number and the documented version disagree. Both the README snippet and `Cargo.toml` still say 0.40.1, and `Cargo.toml` carries an explicit comment to update the version in README.md when changing it. The version gap is a documentation lag of one MSRV-only patch, not a behavioural difference.
Where rusqlite sits against SQLx and an async runtime
The most common comparison for a Rust project is `sqlx`, and the honest summary is that they optimize for different constraints. rusqlite is synchronous, thin, and exposes the full SQLite C API surface including the parts most wrappers skip. `sqlx` is async, compile-time checked against your queries, and works across several database engines. A project that runs on Postgres in production and SQLite in tests may accept `sqlx`; a project that wants SQLite's specific features, writes virtual tables, or manages its own connection lifetime will get more from rusqlite.
The concurrency story follows from that. rusqlite gives you `Connection` objects you own, and Cargo.toml notes the cache feature behaves like SQLite's per-connection prepared statement cache. Sharing a connection across tasks requires your own synchronization; running queries on a thread pool is the usual answer. Related searches turn up `Tokio-rusqlite`, a separate project that supplies a pooled rusqlite connection for tokio, which is a good indication of the shape of the integration rather than something rusqlite provides itself.
The repository has no homepage field, so docs.rs is the reference. Badges point at crates.io for the version, docs.rs for documentation, a GitHub Actions CI badge, an AppVeyor status badge for Windows, codecov for coverage, deps.rs for dependency status, and a Discord invite. `codecov.yml`, `clippy.toml`, `appveyor.yml`, `grcov.sh` and `test.csv` in the tree describe a project with serious test and lint infrastructure, which is what you want from a crate this widely depended on.
One structural note for contributors: `libsqlite3-sys/` is a workspace member, so changes to the FFI layer are a coordinated edit across two crates, and `bindings.md` and `types.md` are the places where the mapping between C and Rust types is written down.
Editorial conclusion
rusqlite is the choice when a Rust project needs SQLite with real control over the API, including the unglamorous parts: virtual tables, blob I/O, tracing hooks and SQLCipher. What you give up relative to an async client is direct concurrency, and what you get is an API surface that maps onto SQLite itself rather than onto a framework. Two decisions to make early: whether to enable `bundled`, which the README argues should be yes for programs controlling their own databases and no for generic libraries, and whether your CI can handle the edition 2024 and Rust 1.88 floor. Note that the README and `Cargo.toml` both still name version 0.40.1 while the newest release is 0.40.2, which only lowered the MSRV, so pinning 0.40.1 is safe but not current.
Frequently asked questions
Can I use SQLite in Rust?
Yes, through rusqlite, an MIT licensed ergonomic wrapper for SQLite from Rust. The base package supports SQLite version 3.45.3 or newer, uses edition 2024 with an MSRV of 1.88.0, and its synchronous API means you decide where blocking calls sit in your runtime.
Should I enable the bundled feature in rusqlite?
For a program that controls its own SQLite databases, the README calls it the right choice, because it avoids depending on a system SQLite that may be old or missing and avoids common build issues. For a generic library, the README advises against it, which is why it is not a default feature.
What changed in rusqlite 0.40?
Version 0.40.0, published 2026-05-26, is breaking and centers on virtual tables: the VTab macros were replaced by constructors, and VTab::best_index, VTab::connect, VTab::create and vtab::dequote were changed or fixed. It also fixed undefined behaviour in ToSqlOutput::from_rc and allowed opting out of sqlite-wasm-rs on wasm32.
What is the newest rusqlite release?
Version 0.40.2, published 2026-08-08, which lowered the MSRV to 1.88.0. The README and Cargo.toml still name 0.40.1, and Cargo.toml carries a comment to update the README when the version changes, so the documentation trails by one MSRV-only patch.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/rusqlite-rusqlite)Star history chart for your README
[](https://hysenlabs.com/projects/rusqlite-rusqlite)Was this useful?