Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Creating Records

Toasty provides two ways to create records: the toasty::create! macro and the create builder. The macro uses a syntax inspired by struct literals and expands to builder calls under the hood. Most code uses the macro; the builder is there when you need programmatic control (e.g., conditional fields).

Creating a single record

With the macro:

#![allow(unused)]
fn main() {
use toasty::Model;
#[derive(Debug, toasty::Model)]
struct User {
    #[key]
    #[auto]
    id: u64,
    name: String,
    #[unique]
    email: String,
}
async fn __example(mut db: toasty::Db) -> toasty::Result<()> {
let user = toasty::create!(User {
    name: "Alice",
    email: "alice@example.com",
})
.exec(&mut db)
.await?;

println!("Created user with id: {}", user.id);
Ok(())
}
}

The macro expands to an equivalent code block as:

#![allow(unused)]
fn main() {
use toasty::Model;
#[derive(Debug, toasty::Model)]
struct User {
    #[key]
    #[auto]
    id: u64,
    name: String,
    #[unique]
    email: String,
}
async fn __example(mut db: toasty::Db) -> toasty::Result<()> {
let user = User::create()
    .name("Alice")
    .email("alice@example.com")
    .exec(&mut db)
    .await?;
Ok(())
}
}

The create! macro does not execute the query. It returns a create builder with all the specified values already applied. Call .exec(&mut db) on the builder to insert the row, or continue working with the builder value (for example, to conditionally set additional fields). The returned User instance has all fields set, including auto-generated ones like id.

Like Rust struct literals, the macro supports field shorthand — writing just name instead of name: name when the variable matches the field name. You can mix shorthand and explicit fields freely.

The generated SQL looks like:

INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');

Field values in the macro can be any Rust expression — literals, variables, or function calls. When a variable has the same name as the field, you can use the shorthand syntax (just name instead of name: name), the same way Rust struct literals work:

#![allow(unused)]
fn main() {
use toasty::Model;
#[derive(Debug, toasty::Model)]
struct User {
    #[key]
    #[auto]
    id: u64,
    name: String,
    #[unique]
    email: String,
}
async fn __example(mut db: toasty::Db) -> toasty::Result<()> {
let name = "Bob";
let user = toasty::create!(User {
    name,
    email: format!("{}@example.com", name.to_lowercase()),
})
.exec(&mut db)
.await?;
Ok(())
}
}

When the variable name differs from the field name, use the explicit field: expr form:

#![allow(unused)]
fn main() {
use toasty::Model;
#[derive(Debug, toasty::Model)]
struct User {
    #[key]
    #[auto]
    id: u64,
    name: String,
    #[unique]
    email: String,
}
async fn __example(mut db: toasty::Db) -> toasty::Result<()> {
let user_name = "Bob";
let user = toasty::create!(User {
    name: user_name,
    email: format!("{}@example.com", user_name.to_lowercase()),
})
.exec(&mut db)
.await?;
Ok(())
}
}

Required vs optional fields

Required fields (String, u64, etc.) must be set before calling .exec(). Optional fields (Option<T>) default to NULL if not set.

#![allow(unused)]
fn main() {
use toasty::Model;
#[derive(Debug, toasty::Model)]
struct User {
    #[key]
    #[auto]
    id: u64,

    name: String,
    bio: Option<String>,
}
async fn __example(mut db: toasty::Db) -> toasty::Result<()> {

// bio defaults to None (NULL in the database)
let user = toasty::create!(User { name: "Alice" })
    .exec(&mut db)
    .await?;

assert!(user.bio.is_none());

// Or set it explicitly
let user = toasty::create!(User {
    name: "Bob",
    bio: "Likes Rust",
})
.exec(&mut db)
.await?;

assert_eq!(user.bio.as_deref(), Some("Likes Rust"));
Ok(())
}
}

Creating through a relation

Use the in keyword to create a record through a relation accessor. The in prefix tells the macro to call .create() on the scope expression — so in user.todos() { ... } expands to user.todos().create().title(...). Toasty sets the foreign key automatically:

#![allow(unused)]
fn main() {
use toasty::Model;
#[derive(Debug, toasty::Model)]
struct User {
    #[key]
    #[auto]
    id: u64,
    name: String,
    #[has_many]
    todos: toasty::HasMany<Todo>,
}
#[derive(Debug, toasty::Model)]
struct Todo {
    #[key]
    #[auto]
    id: u64,
    #[index]
    user_id: u64,
    #[belongs_to(key = user_id, references = id)]
    user: toasty::BelongsTo<User>,
    title: String,
}
async fn __example(mut db: toasty::Db) -> toasty::Result<()> {
let user = toasty::create!(User { name: "Alice" }).exec(&mut db).await?;
let todo = toasty::create!(in user.todos() { title: "Buy groceries" })
    .exec(&mut db)
    .await?;

assert_eq!(todo.user_id, user.id);
Ok(())
}
}

The macro expands to an equivalent code block as:

let todo = user.todos().create()
    .title("Buy groceries")
    .exec(&mut db)
    .await?;

You don’t need to set user_id — Toasty fills it in from the parent because the create builder is scoped to user.todos().

Nested creation

When models have relationships, you can create a parent and its children in a single call. Inside the macro, use { ... } (without a type prefix) for BelongsTo/HasOne fields, and [{ ... }, { ... }] for HasMany fields:

#![allow(unused)]
fn main() {
use toasty::Model;
#[derive(Debug, toasty::Model)]
struct User {
    #[key]
    #[auto]
    id: u64,
    name: String,
    #[has_many]
    todos: toasty::HasMany<Todo>,
}
#[derive(Debug, toasty::Model)]
struct Todo {
    #[key]
    #[auto]
    id: u64,
    #[index]
    user_id: u64,
    #[belongs_to(key = user_id, references = id)]
    user: toasty::BelongsTo<User>,
    title: String,
}
async fn __example(mut db: toasty::Db) -> toasty::Result<()> {
let user = toasty::create!(User {
    name: "Alice",
    todos: [{ title: "Buy groceries" }, { title: "Write docs" }],
})
.exec(&mut db)
.await?;

let todos = user.todos().exec(&mut db).await?;
assert_eq!(2, todos.len());
Ok(())
}
}

The macro expands to an equivalent code block as:

User::create()
    .name("Alice")
    .todos([
        Todo::create().title("Buy groceries"),
        Todo::create().title("Write docs"),
    ])
    .exec(&mut db)
    .await?;

Toasty creates the user first, then creates each todo with the user’s ID automatically set as the foreign key. Nesting works to arbitrary depth — a nested record can itself contain nested records.

Toasty makes a best effort to execute nested creation atomically — either all records are inserted or none are. Whether full atomicity is guaranteed depends on your database’s capabilities, so check your database’s transaction and consistency documentation.

The relationship chapters (BelongsTo, HasMany, HasOne) cover nested creation in more detail.

Creating many records

Same-type batch

Use the ::[ ... ] syntax to create multiple records of the same model:

let users = toasty::create!(User::[
    { name: "Alice", email: "alice@example.com" },
    { name: "Bob", email: "bob@example.com" },
    { name: "Carol", email: "carol@example.com" },
])
.exec(&mut db)
.await?;

The macro expands to an equivalent code block as:

toasty::batch([
    User::create().name("Alice").email("alice@example.com"),
    User::create().name("Bob").email("bob@example.com"),
    User::create().name("Carol").email("carol@example.com"),
])

The same-type batch returns a Vec<User>. The batch is atomic — all records are inserted together or none are.

Mixed-type batch

Use ( ... ) to create records of different models in a single batch:

let (user, post) = toasty::create!((
    User { name: "Alice" },
    Post { title: "Hello World" },
))
.exec(&mut db)
.await?;

The macro expands to an equivalent code block as:

toasty::batch((
    User::create().name("Alice"),
    Post::create().title("Hello World"),
))

You can mix type-target and scoped forms in the same batch:

let (user, todo) = toasty::create!((
    User { name: "Carl" },
    in user.todos() { title: "Buy milk" },
))
.exec(&mut db)
.await?;

Dynamic batches with toasty::batch()

When the number of records is determined at runtime, collect create builders into a Vec and pass it to toasty::batch():

let names = get_names_from_csv();

let mut insertions = vec![];
for (i, name) in names.iter().enumerate() {
    insertions.push(toasty::create!(User {
        name,
        email: format!("user{i}@example.com"),
    }));
}

let users = toasty::batch(insertions).exec(&mut db).await?;

When to use the builder directly

The macro covers the common case. Use the builder directly when you need to conditionally set fields, since the macro requires all fields to be specified in the struct literal:

#![allow(unused)]
fn main() {
use toasty::Model;
#[derive(Debug, toasty::Model)]
struct User {
    #[key]
    #[auto]
    id: u64,
    name: String,
    bio: Option<String>,
}
async fn __example(mut db: toasty::Db) -> toasty::Result<()> {
let mut builder = User::create().name("Alice");

if true /* some condition */ {
    builder = builder.bio("Likes Rust");
}

let user = builder.exec(&mut db).await?;
Ok(())
}
}

Macro-to-builder reference

Each macro form has a direct builder equivalent:

Macro syntaxBuilder equivalent
toasty::create!(User { name: "Alice" })User::create().name("Alice")
toasty::create!(in user.todos() { title: "Buy milk" })user.todos().create().title("Buy milk")
Nested { ... } for BelongsTo/HasOne.field(ChildModel::create().field_calls)
Nested [{ ... }] for HasMany.fields([ChildModel::create()...])
toasty::create!(User::[{ ... }, { ... }])toasty::batch([User::create()...])Vec<User>
toasty::create!((User { ... }, Post { ... }))toasty::batch((User::create()..., Post::create()...)) → tuple

What gets generated

For a model like User, #[derive(Model)] generates:

  • User::create() — returns a builder with a setter for each non-auto field
  • toasty::create!(User { ... }) — macro syntax that expands to builder calls

The create builder’s setter methods accept flexible input types through the IntoExpr trait. For a String field, you can pass &str, String, or &String. For numeric fields, you can pass the value directly or by reference. See Defining Models — What types can you pass to setters? for details.