Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
175: Diesel ORM Schema and Migrations
I've seen this happen to almost every developer the first time they try to evolve their database schema with Diesel. You write your migration, you're feeling good, you update your Rust structs, and then you hit a wall of compiler errors that make it seem like Diesel has forgotten how to read.
// I just added a 'bio' column to my users table in a migration...
// But now this won't compile:
#[derive(Insertable)]
#[diesel(table_name = users)]
pub struct NewUser<'a> {
pub username: &'a str,
pub email: &'a str,
pub bio: &'a str, // Added this!
}
// Compiler Error:
// the trait bound `users::table: diesel::query_builder::Insertable` is not satisfied
// ... because `users::table` does not have a column named `bio`
The Desync Between SQL and schema.rs
The mistake here isn't in the Rust code; it's in the workflow. In Diesel, your schema.rs file is the "source of truth" for the Rust compiler, but your actual database is the source of truth for the runtime. When you write a up.sql migration file, you've told the database to change, but you haven't told the Rust compiler that the database changed.
If you look at your src/schema.rs right now, you'll probably see that the table! { users { ... } } macro is still missing the bio column. Diesel doesn't magically scan your database every time you hit cargo build. It relies on that generated schema.rs file to provide the type-safe mapping.
Updating the Schema via Migrations
To fix this, you need to actually execute the migration and let the Diesel CLI regenerate the schema. Run this in your terminal:
diesel migration run
When you do this, Diesel does two things: it executes the SQL in your up.sql file against your database, and then it looks at the resulting database state to overwrite src/schema.rs. Now, if you open that file, you'll see bio -> Text inside the users table definition. The compiler now knows the column exists, and your NewUser struct will suddenly start working.
Handling Nullable Columns with Option
Now, let's say you made the bio column nullable in SQL because not every user wants to write a biography. You might be tempted to keep bio: &'a str in your Rust struct. This will trigger another, more cryptic error.
In Diesel, a NULL column in SQL must be mapped to an Option<T> in Rust. This is where the type safety actually saves your skin—it forces you to handle the case where the data is missing at compile time, rather than crashing with a null pointer at runtime.
// Fix for a nullable 'bio' column:
#[derive(Insertable)]
#[diesel(table_name = users)]
pub struct NewUser<'a> {
pub username: &'a str,
pub email: &'a str,
pub bio: Option< &'a str >, // Use Option here!
}
The Migration Lifecycle
I always recommend this mental loop: SQL → CLI → Schema → Model. First, write the SQL migration. Second, run diesel migration run to sync the DB and the schema.rs file. Third, update your Rust structs (Models) to match the new schema. If you try to skip to the Model step before running the CLI, you're just fighting the compiler for no reason.
One last tip: if you realize you messed up the migration you just ran, don't manually edit the database. Use diesel migration redo. It rolls back the last migration and runs it again, ensuring your schema.rs and your actual DB stay in perfect lockstep.
📋 Practical Task
Implementing a Product Inventory Schema Extension
You are working on an e-commerce backend. Currently, your products table only has id and name. You need to add two new columns: price (a non-nullable integer representing cents) and stock_count (a nullable integer).
Your goals:
- Create a new migration that adds
price(Integer, NOT NULL) andstock_count(Integer, NULL) to theproductstable. - Execute the migration using the Diesel CLI to update your
schema.rs. - Update your
Productstruct (for querying) and yourNewProductstruct (for inserting) to include these new fields. - Ensure the
stock_countfield is wrapped in anOptionto correctly handle the nullability.
Verification: Attempt to insert a product with a None value for stock_count and verify that it compiles and persists to the database correctly.
There are no comments for now.