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

Next Steps