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
174: SQLx Compile-Time Checked Queries
I've spent way too many hours of my career chasing down bugs that were essentially just typos in a SQL string. You know the feeling: you write a query, it looks perfect, the Rust compiler gives you a thumbs up, and then—boom—the app crashes in production because you wrote user_id instead of userId.
The Danger of Runtime SQL Errors
Let's look at how we usually handle this. I'm building a simple task manager, and I've got a tasks table. I'll start with the standard sqlx::query approach, which is what most people do first.
async fn get_task_title(pool: &PgPool, id: i32) -> Result {
let row = sqlx::query("SELECT title FROM tasks WHERE id = $1")
.bind(id)
.fetch_one(pool)
.await?;
let title: String = row.get("title");
Ok(title)
}
This looks fine. But watch what happens if I make a tiny mistake. I'll change title to tittle in the SELECT statement. I hit cargo build, and... it compiles perfectly. The compiler has no idea what's inside that string. It just sees a string. The error only manifests when this function actually runs, which is the worst possible time to find out you can't spell.
Letting the Compiler Read the Database
This is where SQLx does something almost magical. It provides macros—specifically query!—that actually connect to your database during the compilation process. It sends the SQL to the DB, asks "Is this valid?", and checks the types of the columns being returned.
I'll try replacing my query with the macro version:
async fn get_task_title(pool: &PgPool, id: i32) -> Result {
let row = sqlx::query!("SELECT tittle FROM tasks WHERE id = $1", id)
.fetch_one(pool)
.await?;
Ok(row.tittle)
}
Now, when I run cargo build, the compiler screams at me. It doesn't just say "there's an error"; it tells me exactly what's wrong: column "tittle" does not exist. This is incredible. The Rust compiler is now acting as a SQL linter.
Wait, I just realized I'm getting a new error: can't find DATABASE_URL in environment. This is the "gotcha" with compile-time checks. Since the compiler needs to talk to the DB to verify the SQL, it needs a connection string. I'll create a .env file in the project root:
DATABASE_URL=postgres://user:password@localhost/task_db
Once that's in place, the build succeeds (after I fix my typo, obviously). But there's a catch: I don't want my CI/CD pipeline to need a live database just to compile the code. SQLx handles this with "offline mode." I can run cargo sqlx prepare, and it saves a sqlx-data.json file. This file acts as a cached snapshot of the database schema, allowing the macros to verify queries without a live connection.
Mapping to Structs without the Boilerplate
One thing that always annoyed me about query! is that it returns an anonymous record (a struct created on the fly). If I want to pass that data around my application, I usually want it in a proper struct Task. I used to manually map the fields, but query_as! does the heavy lifting for me.
Let's refine my task fetcher to use a struct:
#[derive(Debug)]
struct Task {
id: i32,
title: String,
is_completed: bool,
}
async fn get_task(pool: &PgPool, id: i32) -> Result {
// Note the 'as Task' part isn't needed; the macro handles the mapping
// based on the struct fields and the column names.
let task = sqlx::query_as!(
Task,
"SELECT id, title, is_completed FROM tasks WHERE id = $1",
id
)
.fetch_one(pool)
.await?;
Ok(task)
}
The beauty here is that if I add a new column to the Task struct but forget to add it to the SELECT statement, the compiler will flag it. It ensures that my Rust types and my database schema are in perfect sync. If the DB column is nullable, SQLx will force the Rust field to be an Option<T>. It effectively eliminates a whole class of "type mismatch" runtime crashes.
📋 Practical Task
Implementing a Type-Safe Project Task Filter
You are tasked with adding a filtering feature to the task manager. You need to create a function that retrieves all tasks that are NOT yet completed, but only for a specific project ID.
- Database Schema: Assume a table
taskswith columnsid (int),project_id (int),title (text), andis_completed (bool). - Requirement 1: Define a
Taskstruct that matches these columns. - Requirement 2: Write an
async fn get_incomplete_tasks_for_projectthat takes a&PgPooland aproject_id: i32. - Requirement 3: Use the
sqlx::query_as!macro to fetch the results. The query should filter byproject_idand ensureis_completedisfalse. - Requirement 4: Ensure the function returns a
Result<Vec<Task>, sqlx::Error>.
Your goal is to ensure that if you were to accidentally misspell project_id as proj_id in the query, the code would fail to compile rather than crashing at runtime.
There are no comments for now.