← All articles
a computer screen with a bunch of text on it

Rust Development Environment: Tooling, IDEs, and Workflow

Languages 2026-02-09 · 5 min read rust cargo rustup development

Rust Development Environment: Tooling, IDEs, and Workflow

Photo by Ferenc Almasi on Unsplash

Rust's tooling is one of its strongest selling points. Cargo handles building, testing, dependency management, and publishing. Clippy catches common mistakes. rust-analyzer provides IDE intelligence that rivals mature ecosystems like Java. The ecosystem is well-integrated, which means less time configuring tools and more time writing code.

Rust programming language logo

Installation and Setup

rustup

rustup manages Rust toolchains. Always install Rust through rustup, not your system package manager.

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Update Rust
rustup update

# Install nightly toolchain (for experimental features)
rustup toolchain install nightly

# Use nightly for a specific project
cd my-project && rustup override set nightly

# Install additional targets (cross-compilation)
rustup target add x86_64-unknown-linux-musl
rustup target add wasm32-unknown-unknown
rustup target add aarch64-apple-darwin

Essential Components

# Formatter
rustup component add rustfmt

# Linter
rustup component add clippy

# Source code for standard library (IDE navigation)
rustup component add rust-src

# Language server
rustup component add rust-analyzer

IDE Support

VS Code with rust-analyzer

rust-analyzer is the standard Rust language server. Install the "rust-analyzer" extension in VS Code.

Key features:

Useful settings:

// .vscode/settings.json
{
  "rust-analyzer.check.command": "clippy",
  "rust-analyzer.cargo.features": "all",
  "rust-analyzer.inlayHints.parameterHints.enable": true,
  "rust-analyzer.inlayHints.typeHints.enable": true,
  "rust-analyzer.lens.references.abi.enable": true,
  "rust-analyzer.procMacro.enable": true
}

Setting check.command to clippy means you get lint warnings directly in the editor, not just compiler errors.

JetBrains RustRover

RustRover is JetBrains' dedicated Rust IDE. It's free for non-commercial use and offers the standard JetBrains experience: excellent refactoring, debugging integration, and database tools.

VS Code vs RustRover: VS Code with rust-analyzer is lighter and faster for small-to-medium projects. RustRover is better for large projects, complex debugging, and developers already in the JetBrains ecosystem.

Cargo Tools

cargo-watch

Re-run commands when source files change:

cargo install cargo-watch

# Rebuild on save
cargo watch -x build

# Run tests on save
cargo watch -x test

# Run clippy on save
cargo watch -x clippy

# Chain commands
cargo watch -x clippy -x test -x "run -- --port 3000"

cargo-edit

Add, remove, and upgrade dependencies from the command line:

cargo install cargo-edit

# Add a dependency
cargo add serde --features derive
cargo add tokio --features full

# Add a dev dependency
cargo add --dev criterion

# Remove a dependency
cargo rm serde

# Upgrade dependencies
cargo upgrade

cargo-expand

See what macros generate:

cargo install cargo-expand

# Expand all macros in a file
cargo expand --lib

# Expand macros for a specific function
cargo expand my_module::my_function

This is essential for debugging derive macros and understanding what #[derive(Serialize)] or #[tokio::main] actually produce.

cargo-deny

Check dependencies for security vulnerabilities, license violations, and banned crates:

cargo install cargo-deny

# Initialize config
cargo deny init

# Run all checks
cargo deny check
# deny.toml
[advisories]
vulnerability = "deny"
unmaintained = "warn"

[licenses]
allow = ["MIT", "Apache-2.0", "BSD-2-Clause", "BSD-3-Clause", "ISC"]
confidence-threshold = 0.8

[bans]
multiple-versions = "warn"

cargo-nextest

A faster test runner that replaces cargo test:

cargo install cargo-nextest

# Run tests (with better output and parallelism)
cargo nextest run

# Run specific tests
cargo nextest run test_name

# Retry flaky tests
cargo nextest run --retries 2

nextest runs each test in its own process, which means better isolation and faster parallel execution. Test output is also cleaner — failures are shown at the end with clear formatting.

Debugging

LLDB / GDB

Rust compiles to native code, so standard debuggers work:

# VS Code: Install "CodeLLDB" extension, then use the debug panel
# Add a launch.json configuration:
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "lldb",
      "request": "launch",
      "name": "Debug",
      "cargo": {
        "args": ["build", "--bin=my-app"],
        "filter": { "kind": "bin" }
      },
      "args": [],
      "cwd": "${workspaceFolder}"
    },
    {
      "type": "lldb",
      "request": "launch",
      "name": "Debug Tests",
      "cargo": {
        "args": ["test", "--no-run"],
        "filter": { "kind": "lib" }
      }
    }
  ]
}

dbg! Macro

Rust's dbg! macro is the printf-debugging equivalent. It prints the expression, its value, and the file/line:

let x = 5;
let y = dbg!(x * 2);  // Prints: [src/main.rs:3] x * 2 = 10

Unlike println!, dbg! returns the value, so you can insert it into expressions without restructuring code.

Common Patterns and Crates

Error Handling

# Cargo.toml
[dependencies]
anyhow = "1"      # For application code (dynamic error types)
thiserror = "2"   # For library code (derive Error trait)
// Application code: anyhow for convenience
use anyhow::{Context, Result};

fn read_config() -> Result<Config> {
    let contents = std::fs::read_to_string("config.toml")
        .context("Failed to read config file")?;
    let config: Config = toml::from_str(&contents)
        .context("Failed to parse config")?;
    Ok(config)
}

// Library code: thiserror for typed errors
use thiserror::Error;

#[derive(Error, Debug)]
pub enum ApiError {
    #[error("Not found: {0}")]
    NotFound(String),
    #[error("Unauthorized")]
    Unauthorized,
    #[error("Database error: {0}")]
    Database(#[from] sqlx::Error),
}

Serialization

[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"

Async Runtime

[dependencies]
tokio = { version = "1", features = ["full"] }
# Or for lighter-weight:
tokio = { version = "1", features = ["rt", "macros", "net"] }

HTTP

[dependencies]
reqwest = { version = "0.12", features = ["json"] }  # Client
axum = "0.8"                                           # Server

Compilation Speed

Rust compilation is famously slow. Here's what actually helps:

# .cargo/config.toml

# Use lld linker (much faster linking)
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=lld"]

[target.x86_64-apple-darwin]
rustflags = ["-C", "link-arg=-fuse-ld=lld"]

[target.aarch64-apple-darwin]
rustflags = ["-C", "link-arg=-fuse-ld=lld"]

# Use cranelift backend in development (faster compilation, slower code)
# Requires nightly
[unstable]
codegen-backend = true

[profile.dev]
codegen-backend = "cranelift"

Biggest wins:

  1. mold/lld linker: Linking is often 50%+ of compile time. mold or lld cuts this dramatically.
  2. sccache: Shared compilation cache across projects: cargo install sccache && export RUSTC_WRAPPER=sccache
  3. Cranelift backend: Faster code generation at the cost of runtime performance. Perfect for development.
  4. Fewer dependencies: Every crate you add increases compile time. Audit your dependency tree.

Recommendations

New to Rust: VS Code + rust-analyzer + CodeLLDB. Install cargo-watch and cargo-edit. Use anyhow for error handling.

Productive setup: Add cargo-nextest (faster tests), cargo-deny (security), and configure lld/mold for faster builds.

Large projects: Consider RustRover, add sccache, and set up Cranelift for dev builds. Profile compile times with cargo build --timings.