← All articles
LANGUAGES Rust Development Environment: Tooling, IDEs, and Wor... 2026-02-09 · 4 min read · rust · cargo · rustup

Rust Development Environment: Tooling, IDEs, and Workflow

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

Rust Development Environment: Tooling, IDEs, and Workflow

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.

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.