Claude
Skills
Sign in
Back

rust-dev

Included with Lifetime
$97 forever

Practical day-1 guide to building applications in Rust well. Covers the mental model (ownership, errors as values, traits-not-interfaces), day-1 decisions (String vs &str, Box vs Rc vs Arc, dyn vs impl Trait, anyhow vs thiserror), idioms to internalize early, anti-patterns to avoid, and a tight crate shortlist (tokio, serde, anyhow, clap, reqwest, tracing, axum, sqlx). Use when starting a new Rust project, learning Rust coming from Python/JS/Go/Java/C++, deciding on types and lifetimes, choosing crates, structuring modules, configuring Cargo.toml/clippy/rustfmt, writing tests, benchmarking, profiling, or speeding up builds, or whenever the user mentions Rust, cargo, ownership, borrow checker, lifetimes, traits, async Rust, testing, or "writing this in Rust".

Backend & APIs

What this skill does


# Rust Development - Day 1

A practical foundation for writing Rust apps well from the first commit. Not a textbook. Focuses on the differences from other languages, the day-1 decisions that shape everything else, and the small set of crates that cover most real apps.

## When to Use

- Starting a new Rust project (CLI, service, library)
- Coming to Rust from Python, JavaScript, Go, Java/C#, or C++
- Choosing between owned/borrowed types, smart pointers, trait objects vs generics
- Picking error handling strategy (`anyhow` vs `thiserror`)
- Deciding which crates to reach for
- Configuring a minimal but opinionated `Cargo.toml`, clippy, and rustfmt

## Day-1 Setup

```bash
# 1. Install the toolchain (rustup is the toolchain manager)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# 2. Confirm components (rustfmt and clippy ship with stable, rust-src enables IDE features)
rustup component add rustfmt clippy rust-src

# 3. Create a project
cargo new my-app          # binary (src/main.rs)
cargo new --lib my-lib    # library (src/lib.rs)

# 4. The dev loop (memorize these four)
cargo check     # fast type-check, no codegen
cargo run       # build and run (binary)
cargo test      # build and run tests (incl. doctests)
cargo clippy    # lint (run before pushing)
cargo fmt       # format

# 5. Manage dependencies without editing Cargo.toml by hand
cargo add tokio --features full
cargo remove tokio
cargo update                 # recompute Cargo.lock within existing semver ranges
```

`cargo update` only moves within the version ranges already in `Cargo.toml`. Crossing a major version (`1.x` to `2.0`) needs a `Cargo.toml` edit or `cargo add <crate>@2`.

**rust-analyzer is mandatory.** It is the language server every editor uses (VS Code, Zed, Neovim, Helix, RustRover uses its own engine but is comparable). In VS Code, install the `rust-analyzer` extension and set `rust-analyzer.check.command` to `"clippy"` so you get lint feedback on save.

**Want a file watcher later?** `cargo install bacon`, then run `bacon` in your project. Not needed on day 1.

## The Rust Mental Model in 5 Ideas

Rust trades two things you take for granted in most languages (a garbage collector and exceptions) for compile-time guarantees about memory, data races, and error handling. The shape of the language follows from that trade.

### 1. Ownership: every value has exactly one owner

Think of values like physical objects. A book, a file, a network connection. At any moment, **one variable owns it**. You can:

- **Move it**: `let b = a;` hands ownership to `b`. `a` is gone.
- **Borrow it immutably**: `&a` lets others look at it. Many readers allowed.
- **Borrow it mutably**: `&mut a` lets one person modify it. Exclusive access.
- **Clone it**: `a.clone()` makes a deep copy. Both keep their own.

When the owner goes out of scope, the value is dropped (memory freed, file closed, lock released). No GC, no manual `free`. This is RAII, enforced by the compiler.

### 2. Aliasing XOR mutability

At any moment, a piece of data has **either**:
- one mutable reference (`&mut T`), **or**
- any number of immutable references (`&T`),

never both. This single rule is what eliminates data races and most use-after-free bugs. The borrow checker enforces it. When it complains, it is telling you your data ownership story is unclear, not that the language is being difficult.

### 3. Errors are values, not exceptions

There is no `try`/`catch`. Functions that can fail return `Result<T, E>`. Functions that can return nothing useful return `Option<T>`. The compiler forces you to handle both. The `?` operator propagates errors up the call stack with one character:

```rust
fn read_config() -> Result<Config, anyhow::Error> {
    let text = std::fs::read_to_string("config.toml")?;  // ? = early-return on Err
    let config = toml::from_str(&text)?;
    Ok(config)
}
```

There is no `null`. `Option<T>` is `None` or `Some(value)`. The compiler will not let you forget the `None` case.

### 4. Traits are not Java interfaces

A `trait` defines behavior. Types `impl` traits. So far so familiar. The differences:

- **Static dispatch is the default.** When you write `fn f<T: Display>(x: T)`, the compiler generates a separate copy of `f` for each concrete `T` you call it with (monomorphization, like C++ templates). Zero runtime overhead.
- **Dynamic dispatch is opt-in** via `dyn Trait` (typically `Box<dyn Trait>` or `&dyn Trait`). One vtable lookup per call.
- **No inheritance.** Traits compose. If you find yourself reaching for `Deref` to "extend" a type, stop and use composition or an enum.
- **Orphan rule**: you can `impl YourTrait for SomeoneElsesType` or `impl SomeoneElsesTrait for YourType`, but not both foreign. This keeps dependency resolution sane.

### 5. The borrow checker is a design oracle

The most common newcomer mistake is treating compiler errors as obstacles to silence. They are not. Almost every borrow-check error reveals a real issue with **who owns what**. When you get stuck, the question is rarely "how do I make this compile" and almost always "what is the actual ownership relationship I want here?" Read the error. The compiler is unusually informative.

## The 3 Questions for Every Function Signature

Before writing a function, ask: does it need to **own**, **read**, or **modify** the input?

```rust
fn consume(s: String)        // owns:    function takes responsibility, caller loses it
fn read(s: &str)             // reads:   function looks at it, caller keeps it
fn modify(s: &mut String)    // mutates: function changes it in place
```

Defaults that work 90% of the time:
- Function parameters: prefer `&str` over `String`, `&[T]` over `Vec<T>` (these are slices, accept both owned and borrowed callers).
- Function returns: return owned types (`String`, `Vec<T>`). Returning references means lifetimes; avoid until you need them.
- Struct fields: prefer **owned** types (`String`, `Vec<T>`). Storing `&str` in a struct is the single most common newcomer trap and it cascades lifetime annotations through every type that holds your struct.

## Day-1 Decision Table

One-line answers to the choices that come up first.

| Decision | Default | When to pick the other |
|---|---|---|
| `String` vs `&str` (struct field) | `String` | Almost never `&str` until you have a real reason and understand lifetimes |
| `String` vs `&str` (function param) | `&str` | Use `String` only if you must own/store it inside |
| `Vec<T>` vs `&[T]` (param) | `&[T]` | `Vec<T>` only if you must own |
| `Box<T>` vs `Rc<T>` vs `Arc<T>` | `Box<T>` (single owner, heap) | `Arc<T>` for shared ownership across threads. Avoid `Rc<T>` as default; use `Arc<T>` so you do not refactor when you go async |
| `RefCell<T>` vs `Mutex<T>` | `Mutex<T>` (or `RwLock<T>`) | Same reason: works in async/threads, while `RefCell` does not |
| `Option<T>` vs `Result<T, E>` | `Option<T>` for "no value", `Result<T, E>` for "failed for a reason" | If the absence carries meaning the caller should handle, `Result` |
| `dyn Trait` vs `impl Trait` / `<T: Trait>` | Generic (`<T: Trait>` or `impl Trait`) - static dispatch | `Box<dyn Trait>` when you need a heterogeneous collection (`Vec<Box<dyn Animal>>`) |
| Errors in app code | `anyhow::Result<T>` everywhere | - |
| Errors in library code | `thiserror`-derived enum | Never `Box<dyn Error>` in public library APIs - forces callers to downcast |
| `&self` vs `&mut self` vs `self` | `&self` for getters, `&mut self` for setters, `self` for builders/consuming ops | - |
| Module layout | Inline modules until a file gets long, then split | One module = one file is a Java/C# instinct, not a Rust one |

## Idioms to Internalize Early

These appear in nearly every Rust program. Learn them in week 1.

**`?` for error propagation.** Replaces nine lines of `match` with one character.
```rust
let body = reqwest::get(url).await?.text().await?;
```

**Iterator chains over manual loops.** Compile to the same machine cod
Files: 11
Size: 103.9 KB
Complexity: 65/100
Category: Backend & APIs

Related in Backend & APIs