←Back to Documentation
Rust SDK
Memory-safe, zero-cost abstractions for entropyDB with async/await
Overview
The Rust SDK provides:
- • Memory Safety: No null pointers or data races
- • Zero-Cost: Abstractions with no runtime overhead
- • Async/Await: Tokio-based async runtime
- • Type Safety: Compile-time query validation
- • Performance: Optimized for high throughput
Installation
# Add to Cargo.toml
[dependencies]
entropydb = { path = "../sdks/rust" } # or a published version once released
tokio = { version = "1", features = ["full"] }Basic Usage
use entropydb::{Client, Error};
struct User {
id: i64,
username: String,
email: String,
}
#[tokio::main]
async fn main() -> Result<(), Error> {
// Connect to EntropyDB (Host/Port point at entropydb.pg-protocol.port; User/Password are the
// email/password you registered with).
let client = Client::builder()
.host("localhost")
.port(5432)
.database("mydb")
.user("admin@example.com")
.password("password")
.pool_size(20)
.build()
.await?;
// Create table. Note: no SERIAL/auto-increment -- EntropyDB's SQL engine doesn't support
// sequence/identity columns today, so IDs are assigned by the caller.
client.execute(
"CREATE TABLE IF NOT EXISTS users (
id INTEGER,
username VARCHAR(64),
email VARCHAR(255),
PRIMARY KEY (id)
)",
&[],
).await?;
// Insert data
let rows_affected = client.execute(
"INSERT INTO users (id, username, email) VALUES ($1, $2, $3)",
&[&1i64, &"alice", &"alice@example.com"],
).await?;
println!("Inserted {} row(s)", rows_affected);
// Query single row
let row = client.query_one(
"SELECT id, username, email FROM users WHERE username = $1",
&[&"alice"],
).await?;
let user = User {
id: row.get("id"),
username: row.get("username"),
email: row.get("email"),
};
println!("User: {} <{}>", user.username, user.email);
// Query multiple rows
let rows = client.query(
"SELECT id, username, email FROM users WHERE email LIKE $1",
&[&"%@example.com"],
).await?;
for row in rows {
let username: String = row.get("username");
println!("Username: {}", username);
}
Ok(())
}Query Builder
No query! macro. Some Rust Postgres crates (e.g. sqlx) validate SQL at compile time by introspecting pg_catalog/information_schema against a live database. EntropyDB's wire-protocol server doesn't implement those system catalogs, so that whole feature class can't work here — this SDK doesn't offer it or pretend to.
use entropydb::query::{Select, Delete};
// Select/Delete builders assemble real SQL text with PostgreSQL's native $N placeholders.
// You supply your own typed params, in the same order predicates were added -- Rust's
// tokio_postgres bind parameters are borrowed trait objects tied to your own local
// variables' lifetimes, so a builder can't also own and hand back a Vec of them.
let sql = Select::new(&["id", "username", "email"])
.from("users")
.where_clause("email LIKE ?")
.and("created_at > ?")
.order_by("created_at DESC")
.limit(10)
.build();
// sql: SELECT id, username, email FROM users WHERE email LIKE $1 AND created_at > $2 ORDER BY created_at DESC LIMIT 10
let rows = client.query(&sql, &[&"%@example.com", &cutoff]).await?;
let delete_sql = Delete::new("users").where_clause("id = ?").build();
client.execute(&delete_sql, &[&user_id]).await?;Transactions
use entropydb::{Client, Error};
// Client::get() checks out a real pooled connection; Transaction borrows from it, so both stay
// in this function's own local scope -- see the SDK's own README for why there's no
// Client::transaction(|tx| async { ... }) closure helper (a real Rust async-lifetime limitation,
// not an oversight).
async fn transfer_funds(
client: &Client,
from_id: i64,
to_id: i64,
amount: f64,
) -> Result<(), Error> {
let mut conn = client.get().await?;
let tx = conn.transaction().await?;
tx.execute(
"UPDATE accounts SET balance = balance - $1 WHERE id = $2",
&[&amount, &from_id],
).await?;
tx.execute(
"UPDATE accounts SET balance = balance + $1 WHERE id = $2",
&[&amount, &to_id],
).await?;
tx.execute(
"INSERT INTO transfers (from_account, to_account, amount) VALUES ($1, $2, $3)",
&[&from_id, &to_id, &amount],
).await?;
tx.commit().await?;
Ok(())
}
// Rollback: drop the transaction without calling .commit() -- real tokio_postgres semantics roll
// back automatically, EntropyDB's TransactionCoordinator included.
async fn rolled_back_on_error(client: &Client) -> Result<(), Error> {
let mut conn = client.get().await?;
let tx = conn.transaction().await?;
tx.execute("INSERT INTO users (id, username) VALUES ($1, $2)", &[&1i64, &"alice"]).await?;
if some_check_failed() {
return Ok(()); // tx dropped here without commit() -- a real rollback
}
tx.commit().await?;
Ok(())
}
fn some_check_failed() -> bool { false }Connection Pooling
use entropydb::Client;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::builder()
.host("localhost")
.port(5432)
.database("mydb")
.user("admin@example.com")
.password("password")
.pool_size(20)
.connect_timeout(Duration::from_secs(10))
.build()
.await?;
// Get pool stats -- a real deadpool_postgres::Status, not a custom type.
let stats = client.pool_stats();
println!("Available connections: {}", stats.available);
println!("Waiting for a connection: {}", stats.waiting);
println!("Pool size: {}", stats.size);
Ok(())
}Async Streams
use futures::stream::StreamExt;
async fn process_large_result(client: &Client) -> Result<(), Error> {
// query_raw streams rows (a real tokio_postgres::RowStream) instead of collecting them into
// a Vec first -- for a result set too large to hold in memory all at once.
let mut stream = client.query_raw(
"SELECT id FROM large_table WHERE status = $1",
&[&"active"],
).await?;
while let Some(row) = stream.next().await {
let row = row?;
let id: i64 = row.get("id");
process_row(id).await?;
}
Ok(())
}
async fn process_row(_id: i64) -> Result<(), Error> { Ok(()) }
// Concurrent queries: Client is Clone (the pool is reference-counted internally), so each task
// gets its own pooled connection. Note: EntropyDB's SQL engine doesn't support standalone
// aggregates like COUNT(*) outside GROUP BY today -- a real, disclosed limitation -- so this
// counts rows client-side via .len() rather than a SQL COUNT(*).
async fn concurrent_queries(client: &Client) -> Result<(), Error> {
let (users, orders, products) = tokio::try_join!(
client.query("SELECT id FROM users", &[]),
client.query("SELECT id FROM orders", &[]),
client.query("SELECT id FROM products", &[]),
)?;
for (label, rows) in [("users", users), ("orders", orders), ("products", products)] {
println!("{label}: {}", rows.len());
}
Ok(())
}Best Practices
Safety
- • Use prepared statements
- • Leverage type system for validation
- • Handle errors with Result types
- • Use compile-time query checking
Performance
- • Use connection pooling
- • Stream large result sets
- • Batch operations when possible
- • Use zero-copy operations