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.

Max Wells

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:

No spam. Unsubscribe any time.


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!. Always query_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 adds RETURNING with all columns. Use query_as! + .fetch_one().

Prompt to copy:

"Implement [X] in [db.rs]. Use query_as! returning [YourStruct]. For mutations, add RETURNING with all columns and use .fetch_one(). Never use sqlx::query!. Follow the exact pattern in article_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. Return Result<(), ServerFnError>. Errors use ServerFnError::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_auth crate. The type User is defined in app_auth::user. The HasPermission trait is also in app_auth. Read app_crates/app_auth/src/user.rs and app_crates/app_auth/src/permissions.rs before writing anything."

Prompt for a new domain feature:

"Add a new function to app/src/domain/[name]/db.rs. Read app/src/domain/article_visits/article_visit_db.rs for the exact pattern: struct with #[derive(sqlx::FromRow)] gated behind ssr, 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]. Read article_visit_db.rs for the pattern. Use query_as! returning [YourStruct]. Use query_scalar! for the EXISTS check. Add RETURNING with all columns. The unid parameter 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. Return Result<(), ServerFnError>. Errors use ServerFnError::new(err.to_string())."

Shared struct (SSR + WASM):

"This struct is used in both SSR and hydrate targets. Gate sqlx::FromRow with #[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]. Keep serde::Serialize and serde::Deserialize ungated, 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. Future is not Send. Ask: "Which async boundary requires Send? 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!, always query_as! or query_scalar!
  • SSR imports go inside #[server] body, not at top
  • Gate SQLx derives: #[cfg_attr(feature = "ssr", ...)]
  • tokio::spawn in server fn / spawn_local in 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 run
  • cargo nextest run -p app --features ssr
  • cargo check --features ssr
  • cargo check --features hydrate
  • cargo 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.

Book a session →


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.

No spam. Unsubscribe any time.

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

Your Future Awaits

Join our 9-week self-paced bootcamp and go from one language to production Rust. Learn through hands-on projects and daily async support.

Book a Call