Skip to content
Tutorials

Your first entity

Maya signs up for the writing app. Start with her User and an in-memory SQLite database. Rent generates a Rust client from the RSL schema. Your application gets typed builders instead of stringly table and column names.

use generated::user_create::NewUser;

let maya = client
    .create_user_from(NewUser {
        name: "Maya".to_owned(),
    })
    .id(1)
    .save()
    .await?;

let maya = client
    .user()
    .id_eq(maya.id)
    .only()
    .await?;

let maya = client
    .update_user_one(&maya)
    .name("Maya Chen")
    .save_one()
    .await?;

client
    .delete_user_one(&maya)
    .exec()
    .await?;

NewUser is generated from required create fields, so the compiler points out a missing required value before the query runs. For conditional or progressively assembled input, use the fluent create_user() builder instead.

only() requires exactly one row. Use only_or_none() for optional lookup and all() for a collection. Mutation builders do nothing until save(), save_one(), or exec() is awaited.

Complete example

Download this project, or copy the files below into an empty directory.

Replace RENT_REPOSITORY_URL in Cargo.toml with the URL supplied with your preview access. Use the matching Rent CLI release.

rent generate
cargo run

The project includes its database setup. Each lesson uses a small, self-contained schema. You can run it without applying the earlier lessons.

Cargo.toml
[package]
name = "rent-lesson-01-crud"
version = "0.1.0"
edition = "2024"

[workspace]

[[bin]]
name = "rent-lesson-01-crud"
path = "main.rs"

[dependencies]
rent = { git = "RENT_REPOSITORY_URL", tag = "v0.8.6", default-features = false, features = ["sqlite", "migrate", "extensions"] }
anyhow = "1.0.104"
tokio = { version = "1.53.1", features = ["full"] }
rent.toml
schema = "schema.rsl"
generated = "generated"
migrations = "migrations"

[database]
dialect = "sqlite"
backends = ["sqlite"]
schema.rsl
datasource db {
  provider = "sqlite"
}

model User {
  id   BigInt  @id
  name String

  @@map("users")
}
main.rs
use anyhow::{Result, ensure};
use rent::driver::DatabasePool;
use rent::sql::Dialect;

#[allow(dead_code, unused_imports)]
#[rustfmt::skip]
#[path = "generated/mod.rs"]
mod generated;

async fn exercise() -> Result<()> {
    let pool = DatabasePool::connect(Dialect::Sqlite, "sqlite::memory:").await?;
    pool.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)")
        .await?;
    let client = generated::client::Client::new(pool);

    let maya = client
        .create_user_from(generated::user_create::NewUser {
            name: "Maya".to_owned(),
        })
        .id(1)
        .save()
        .await?;
    ensure!(client.user().id_eq(maya.id).only().await? == maya);

    let maya = client.update_user_one(&maya).name("Maya Chen").save_one().await?;
    ensure!(maya.name == "Maya Chen");

    ensure!(client.delete_user_one(&maya).exec().await? == 1);
    ensure!(!client.user().exist().await?);
    Ok(())
}

#[tokio::main]
async fn main() -> Result<()> {
    exercise().await
}

#[cfg(test)]
mod tests {
    #[tokio::test]
    async fn example_runs() -> anyhow::Result<()> {
        super::exercise().await
    }
}

On this page