How I Use Claude Code to Build Rust Apps Faster
My exact prompts, workflow, and practical patterns for using Claude Code in real-world Rust projects: SQLx, feature flags, server functions, workspace crates.
My name is Max and I'm the founder of Rustify.
I've been writing Rust professionally for 2 years and I've helped dozens of engineers make the switch to Rust professionally.
If you want to go further, here's how I can help:
- Fullstack Bootcamp: structured program with real projects, async support, and private community access.
- Blockchain Bootcamp: structured program with real projects, async support, and private community access.
- 1:1 Mentorship: personalized sessions to get you hired faster, with projects tailored to you and mock interviews.
And if you want more Rust content, I post regularly on my YouTube channel:

This guide is for Rust developers: it assumes you know the language. If you're building with Leptos, SQLx, or a Cargo workspace, this will save you hours.
Claude Code will burn you on Rust. Here's exactly how to avoid it.
I've been building production Rust apps with Claude Code for months. It made me significantly faster. It also burned me, badly, a few times.
The problems aren't random. They're predictable. Below are the exact traps and the exact prompts I use to route around them, all from a real production codebase.
1. Set Up Claude Code for Your Rust Project
CLAUDE.md is Claude Code's memory. A markdown file at your repo root that Claude Code reads automatically every session. Without it, it treats your project like any generic Rust codebase, and makes generic mistakes.
Here's the minimum you need:
### Testing
- Use `cargo nextest run` (NOT `cargo test`)
- For DB tests: `cargo nextest run -p app --features ssr`
### SQL & SQLx
- NEVER use `sqlx::query!`
- Use `query_as!` (rows), `query_scalar!` (single value)
- Every mutation: add `RETURNING` + all columns
### Workspace
- Auth logic → `app_crates/app_auth`
- Shared types → `app_crates/app_types`
- Never put SSR-only code without `#[cfg(feature = "ssr")]`The difference between vague and enforceable:
- ❌ "Follow Rust best practices." Claude Code ignores this. It thinks it always does.
- ✅ "Never use
sqlx::query!. Alwaysquery_as!." This is a constraint it can actually check.
The 3-file rule: Before touching any file, tell Claude Code to read 3 existing files that use similar patterns. In a Rust workspace, db.rs in one domain is not the same as another. Reading existing files prevents style drift and broken conventions.
Want the production-ready CLAUDE.md for Leptos + SQLx + Cargo workspaces? It's included in the Rustify Bootcamp. Pre-tuned, battle-tested, ready to drop into your repo. Fullstack Bootcamp →
2. SQLx: The Rule That Changes Everything
Claude Code defaults to sqlx::query! every single time. The internet is full of query! examples. Claude Code learned from the internet. Every single client I work with hits this on the first day without exception.
Why query! breaks your setup: It requires a live database connection at compile time. Breaks in CI, breaks offline, breaks when the schema changes. It also returns anonymous row types, not your domain structs.
The three rules:
- SELECT →
query_as!: maps rows directly to your struct (must#[derive(sqlx::FromRow)]) - Single value →
query_scalar!: for EXISTS checks, COUNT, fetching a single unid. Use.fetch_optional(). - Mutations →
RETURNING: every INSERT / UPDATE addsRETURNINGwith all columns. Usequery_as!+.fetch_one().
Prompt to copy:
"Implement [X] in [db.rs]. Use
query_as!returning[YourStruct]. For mutations, addRETURNINGwith all columns and use.fetch_one(). Never usesqlx::query!. Follow the exact pattern inarticle_visit_db.rs."
3. Feature Flags: The Invisible Wall
A Leptos app compiles twice: once for the server (ssr feature, with DB access, Axum, filesystem) and once for the browser (hydrate feature, WASM, no std threads, no DB). Claude Code doesn't know which world it's writing for.
The three traps:
Trap 1: SQLx derive without feature gate
Claude Code writes #[derive(sqlx::FromRow)]. This fails to compile on WASM because sqlx doesn't exist in the hydrate target. The fix: #[cfg_attr(feature = "ssr", derive(sqlx::FromRow))].
Trap 2: DB imports at the top of the file
Claude Code puts use crate::common::app_state::use_app_state; at the top of the file. That import only exists under ssr. It must go inside the #[server] function body, not at the module level.
Trap 3: tokio::spawn in client-side code
tokio::spawn works fine inside a #[server] function. But in a Leptos component or client event handler, you're in WASM, use leptos::spawn_local instead. Claude Code doesn't distinguish.
Prompt to copy:
"This struct is used in both SSR and WASM. Gate any SQLx or server-only derives with
#[cfg_attr(feature = "ssr", derive(...))]. Never import server-only crates at module level."
Stuck on WASM compile failures right now? Feature gate mismatches and SSR/hydrate boundary bugs are the #1 thing I debug in 1:1 sessions. I review your actual codebase and fix it with you. Book a mentorship session →
4. Server Functions: What Always Goes Wrong
#[server] marks a function that runs only on the server but can be called from the client. Leptos generates a WASM stub that makes an HTTP request. The function body is SSR-only, and all imports inside it must also be SSR-only.
What Claude Code does: puts use crate::common::app_state::use_app_state; at the top of the file, outside the function. This compiles on SSR but fails on WASM.
The correct pattern: SSR imports go inside the function body.
Prompt to copy:
"Add a server fn to
app/src/domain/[name]/services.rs. All SSR imports (use_app_state, db module) go inside the function body, not at the module level. ReturnResult<(), ServerFnError>. Errors useServerFnError::new(err.to_string())."
Also: tokio::spawn is fine inside a server function (you're in SSR context). In a Leptos component or event handler, use spawn_local.
5. Workspace Crates: Tell Claude Code Where Code Lives
Claude Code sees your files but doesn't understand your crate architecture. Without guidance, it puts auth logic in app, shared types in app, database code in app, everything in app. That collapses domain separation and creates circular dependencies.
The orphan rule (Claude Code's most frustrating mistake): You can only implement a trait for a type if you own the trait or the type. Claude Code often tries to implement a trait from crate A for a type from crate B, inside crate C, which is illegal. The impl must live in app_auth if that's where User is defined.
Prompt for cross-crate code:
"This belongs in the
app_authcrate. The typeUseris defined inapp_auth::user. TheHasPermissiontrait is also inapp_auth. Readapp_crates/app_auth/src/user.rsandapp_crates/app_auth/src/permissions.rsbefore writing anything."
Prompt for a new domain feature:
"Add a new function to
app/src/domain/[name]/db.rs. Readapp/src/domain/article_visits/article_visit_db.rsfor the exact pattern: struct with#[derive(sqlx::FromRow)]gated behindssr,query_as!for SELECT,query_scalar!for EXISTS."
6. Prompt Templates That Actually Work
Every good Rust prompt has four parts: context (which crate, feature, domain), files to read (3 specific existing files), types (concrete Rust types, not "a list of X"), constraints (no query!, no top-level SSR imports, no API changes).
New SQLx query:
"In
app/src/domain/[name]/db.rs, add a function to [do X]. Readarticle_visit_db.rsfor the pattern. Usequery_as!returning[YourStruct]. Usequery_scalar!for the EXISTS check. AddRETURNINGwith all columns. Theunidparameter is.to_uuid()."
New server function:
"Add a server fn to
app/src/domain/[name]/services.rs. All SSR imports (use_app_state, db module) go inside the function body, not at the module level. ReturnResult<(), ServerFnError>. Errors useServerFnError::new(err.to_string())."
Shared struct (SSR + WASM):
"This struct is used in both SSR and hydrate targets. Gate
sqlx::FromRowwith#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]. Keepserde::Serializeandserde::Deserializeungated, those work in both."
Refactor without scope creep:
"Read [file_a, file_b, file_c]. Refactor [fn] to [goal]. Do NOT change the public API. Do NOT add abstraction. Do NOT touch other functions. One function, one change."
Compiler error in async code:
"Full error below. This is inside a
#[server]async function using Tokio. Explain the root cause, specifically whether this is a lifetime, Send bound, or borrow issue. Then show the minimal fix."
7. Compiler Error Workflow
"The first line of the error is rarely the problem. The note and help lines usually are." Always scroll down.
Always include:
- Full error text: the E-code, the message, the note, the help
- Active features: is this SSR or hydrate? Claude Code needs to know the target
- Function signature: especially if the error involves lifetimes or async bounds
- The relevant file: tell it to read the file before answering
Common Rust error patterns:
- E0277, trait not satisfied: Common in async.
Futureis notSend. Ask: "Which async boundary requiresSend? Can I restructure to avoid it?" - E0502, borrow conflict: Borrow across await points. Ask: "Which borrow spans an await? Should I clone, restructure, or use an Arc?"
- E0425, name not found: Usually a missing feature gate. Ask: "Is this type gated behind
#[cfg(feature = "ssr")]? Should the import move inside the function body?" - E0119, conflicting impl: Orphan rule. Ask: "In which crate does the type live? In which crate does the trait live? The impl must be in one of those."
Quick Reference
The non-negotiable rules:
- Never
query!, alwaysquery_as!orquery_scalar! - SSR imports go inside
#[server]body, not at top - Gate SQLx derives:
#[cfg_attr(feature = "ssr", ...)] tokio::spawnin server fn /spawn_localin components- Trait impl must be in same crate as the type
RETURNING+ all columns on every mutation- Read 3 files before writing anything
Commands:
cargo nextest runcargo nextest run -p app --features ssrcargo check --features ssrcargo check --features hydratecargo clippy -- -D warnings
Claude Code's blind spots: your crate boundaries, which feature target you're in, locked dependency versions, your domain conventions, the orphan rule across crates.
There are Rust developers who use Claude Code and still lose hours to the same class of mistakes, session after session. Wrong feature target, broken SQLx query, orphan rule violation, hallucinated API. It compounds. The tool feels unreliable.
And there are developers who've internalized where Claude Code breaks on Rust specifically, built the right CLAUDE.md from the start, and now use it as a real multiplier. Same tool. Different result.
If you want to get to the second position faster, with a real codebase and direct feedback on what's costing you time, that's what 1:1 mentorship is for. 3 months.
If you want me to walk through this on your actual codebase, reviewing your code, catching mistakes before they cost you hours, that's exactly what the Rustify Bootcamp and 1:1 Mentorship are for:
- Fullstack Bootcamp: structured program with real projects, async support, and private community access.
- Blockchain Bootcamp: structured program with real projects, async support, and private community access.
- 1:1 Mentorship: personalized sessions to get you hired faster, with projects tailored to you and mock interviews.

Student Success Stories
Hear from engineers who built real Rust projects with Rustify
Arik Dutta
Technical Lead · Low-code & Python → Rust
Tiago Afonso
Fullstack Developer
Ugo Tiberto
Rust Engineer · Fullstack Developer