Go to Rust Series: ← Hello World Comparison | Series Overview | The Rust Compiler →


Installation: First Impressions

Go: Download and Done

Installation:

  1. Go to golang.org/dl
  2. Download installer for your OS
  3. Run installer
  4. Done

Verify:

$ go version
go version go1.22.0 linux/amd64

That’s it. Go is installed. One binary, one version, ready to use.

Where it lives:

$ which go
/usr/local/go/bin/go

Single installation directory. Simple.

Rust: The Rustup Way

Installation:

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

This installs rustup, Rust’s toolchain manager (like nvm for Node or rbenv for Ruby).

What gets installed:

  • rustup - Toolchain manager
  • rustc - Rust compiler
  • cargo - Package manager and build tool
  • rust-docs - Offline documentation
  • rust-std - Standard library
  • rustfmt - Code formatter
  • clippy - Linter

Verify:

$ rustc --version
rustc 1.77.0 (aedd173a2 2024-03-17)

$ cargo --version
cargo 1.77.0 (3fe68eabf 2024-02-29)

$ rustup --version
rustup 1.27.0 (bbb9276d2 2024-03-08)

Three commands instead of one. More complex, but more powerful.

Philosophy: One Version vs Many

Go: One Version at a Time

Go installs a single version:

$ go version
go version go1.22.0 linux/amd64

To use multiple versions:

  • Install them manually in different directories
  • Use go get golang.org/dl/go1.21.0 and then go1.21.0 download
  • Or use third-party tools like gvm

Most Go developers use one version and upgrade occasionally.

Rust: Toolchain Manager Built-In

Rustup manages multiple toolchains:

# Install specific version
$ rustup install 1.76.0

# Install nightly
$ rustup install nightly

# Install beta
$ rustup install beta

# List installed toolchains
$ rustup toolchain list
stable-x86_64-unknown-linux-gnu (default)
nightly-x86_64-unknown-linux-gnu
1.76.0-x86_64-unknown-linux-gnu

# Switch default
$ rustup default nightly

# Use specific version for current directory
$ rustup override set 1.76.0

Per-project toolchain:

# rust-toolchain.toml
[toolchain]
channel = "1.76.0"
components = ["rustfmt", "clippy"]

Now that project always uses Rust 1.76.0, regardless of your default.

Winner: Rust - Built-in version management is excellent.

IDE Setup

Go: gopls (Language Server)

VS Code:

  1. Install “Go” extension
  2. It installs gopls automatically
  3. Done

What you get:

  • Autocomplete
  • Go to definition
  • Find references
  • Inline documentation
  • Automatic formatting with gofmt
  • Import management

gopls configuration:

{
  "go.useLanguageServer": true,
  "go.lintTool": "golangci-lint",
  "go.formatTool": "gofmt"
}

Rust: rust-analyzer (Language Server)

VS Code:

  1. Install “rust-analyzer” extension
  2. Done (uses rustup-installed toolchain)

What you get:

  • Autocomplete (better than gopls)
  • Go to definition
  • Find references
  • Inline type hints
  • Macro expansion viewing
  • Inline error messages
  • Automatic imports

rust-analyzer configuration:

{
  "rust-analyzer.checkOnSave.command": "clippy",
  "rust-analyzer.cargo.features": "all",
  "editor.formatOnSave": true
}

Rust-analyzer is MORE powerful than gopls:

Example: Inline type hints

let nums = vec![1, 2, 3];  // rust-analyzer shows: Vec<i32>
let doubled = nums.iter().map(|x| x * 2);  // Shows: Map<Iter<i32>, [closure]>

You can see inferred types without hovering.

Example: Macro expansion

println!("Hello {}", name);

Rust-analyzer can show you what this macro expands to—super helpful for understanding macros.

Winner: Rust - rust-analyzer is exceptional.

Formatting

Go: gofmt (One True Style)

Go has one official formatter with zero configuration:

$ gofmt -w .

Or in your editor, it formats on save. There are no debates about style:

  • Tabs, not spaces (fight me)
  • Opening braces on same line
  • No semicolons

Example:

// Before
func add(a,b int)int{return a+b}

// After gofmt
func add(a, b int) int { return a + b }

Everyone’s Go code looks identical. No bikeshedding.

Rust: rustfmt (Also Opinionated, But Configurable)

Rust has one official formatter but with some configuration:

$ cargo fmt

Configuration (rustfmt.toml):

max_width = 100
tab_spaces = 4
use_small_heuristics = "Max"

Example:

// Before
fn add(a:i32,b:i32)->i32{a+b}

// After rustfmt
fn add(a: i32, b: i32) -> i32 {
    a + b
}

Winner: Tie - Both are excellent. Go is stricter (good for consistency), Rust allows some tweaking (good for flexibility).

Linting

Go: go vet + External Linters

Built-in:

$ go vet ./...

Basic checks: unused variables, impossible comparisons, etc.

Advanced (golangci-lint):

$ golangci-lint run

Runs 50+ linters. Configuration:

# .golangci.yml
linters:
  enable:
    - gofmt
    - govet
    - errcheck
    - staticcheck
    - unused

Rust: clippy (Built-in, Amazing)

Run clippy:

$ cargo clippy

Example output:

warning: this looks like you are trying to swap `a` and `b`
  --> src/main.rs:4:5
   |
4  |     let temp = a;
   |     _____________^
5  | |   a = b;
6  | |   b = temp;
   | |____________^ help: try: `std::mem::swap(&mut a, &mut b)`

It suggests std::mem::swap! It teaches you idiomatic Rust.

More examples:

// You write:
let x = if condition { 5 } else { 5 };

// Clippy warns:
help: consider using: let x = 5;
// You write:
for i in 0..vec.len() {
    println!("{}", vec[i]);
}

// Clippy suggests:
help: consider using: `for item in &vec`

Winner: Rust (clippy) - It’s not even close. Clippy is incredible.

Documentation

Go: godoc

Generate docs:

$ go install golang.org/x/tools/cmd/godoc@latest
$ godoc -http=:6060

Open browser to localhost:6060 to see docs.

Or:

$ go doc fmt.Println

Documentation is extracted from comments:

// Println formats using the default formats and writes to standard output.
// Spaces are added between operands and a newline is appended.
func Println(a ...interface{}) (n int, err error)

Rust: cargo doc

Generate docs:

$ cargo doc --open

Automatically opens in browser. Documentation is generated from doc comments:

/// Adds two numbers together.
///
/// # Examples
///
/// ```
/// let result = add(2, 3);
/// assert_eq!(result, 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

The example code is tested:

$ cargo test --doc

Winner: Rust - Documentation tests are brilliant.

Debugging

Go: Delve

Install:

$ go install github.com/go-delve/delve/cmd/dlv@latest

Debug:

$ dlv debug
(dlv) break main.main
(dlv) continue
(dlv) print myVar

Or use IDE integration (works great in VS Code).

Rust: rust-gdb / rust-lldb

Debug with gdb:

$ rust-gdb target/debug/myapp
(gdb) break main
(gdb) run
(gdb) print my_var

Or use IDE integration. Rust-analyzer integrates with CodeLLDB extension in VS Code.

Winner: Tie - Both have good debuggers, similar IDE integration.

Cross-Compilation

Go: Built-in and Easy

Compile for different platforms:

# Linux
$ GOOS=linux GOARCH=amd64 go build

# Windows
$ GOOS=windows GOARCH=amd64 go build

# macOS ARM (M1/M2)
$ GOOS=darwin GOARCH=arm64 go build

# List all supported platforms
$ go tool dist list

Zero setup needed. Cross-compilation “just works” for most targets.

Rust: Requires Setup

Install target:

$ rustup target add x86_64-pc-windows-gnu
$ rustup target add x86_64-apple-darwin

Compile:

$ cargo build --target x86_64-pc-windows-gnu

List targets:

$ rustup target list

Caveat: Some targets require additional linkers and toolchains:

# For Windows from Linux, you need mingw
$ sudo apt-get install mingw-w64

# Configure Cargo
# .cargo/config.toml
[target.x86_64-pc-windows-gnu]
linker = "x86_64-w64-mingw32-gcc"

Winner: Go - Cross-compilation is significantly easier.

Dependency Management

Go: go.mod + go.sum

Initialize:

$ go mod init myproject

Add dependency:

$ go get github.com/gorilla/[email protected]

Update dependencies:

$ go get -u ./...
$ go mod tidy

Rust: Cargo.toml + Cargo.lock

Initialize:

$ cargo new myproject

Add dependency:

$ cargo add tokio --features full

Update dependencies:

$ cargo update

Winner: Tie - Both are good. Covered in detail in Cargo vs Go Modules.

Testing

Go: Built-in

Write test:

// add_test.go
package main

import "testing"

func TestAdd(t *testing.T) {
    result := add(2, 3)
    if result != 5 {
        t.Errorf("Expected 5, got %d", result)
    }
}

Run tests:

$ go test
$ go test -v
$ go test -cover

Rust: Built-in with More Features

Write test:

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_add() {
        assert_eq!(add(2, 3), 5);
    }

    #[test]
    #[should_panic]
    fn test_divide_by_zero() {
        divide(10, 0);
    }
}

Run tests:

$ cargo test
$ cargo test --lib
$ cargo test test_add
$ cargo test -- --nocapture  # Show println! output

Winner: Rust - More testing features (benchmarks, doc tests, etc.).

Build Times

Go: Fast

Simple project:

$ time go build
real    0m0.234s

Medium project (with dependencies):

$ time go build
real    0m2.451s

Go’s compile times are legendary. Even large projects build in seconds.

Rust: Slower (But Getting Better)

Simple project:

$ time cargo build
real    0m0.612s

Medium project (with dependencies):

$ time cargo build --release
real    0m45.123s

With caching (incremental builds):

$ touch src/main.rs
$ time cargo build --release
real    0m1.832s

First builds are slow. Incremental builds are much better.

Winner: Go - Significantly faster iteration.

The Complete Setup: Side-by-Side

Go Developer Setup (30 minutes)

  1. Download and install Go (5 min)
  2. Install VS Code + Go extension (5 min)
  3. Install golangci-lint (2 min)
  4. Write code (immediately productive)

Total investment: < 30 minutes

Rust Developer Setup (1 hour)

  1. Install rustup (5 min)
  2. Install VS Code + rust-analyzer (5 min)
  3. Configure rust-analyzer (5 min)
  4. Learn cargo commands (15 min)
  5. Fight with the borrow checker (30 min)
  6. Write code (productive after initial learning curve)

Total investment: ~1 hour to a few days

Tooling Quality: Subjective Impressions

Go’s Strengths

  • Speed: Compile times are unbeatable
  • Simplicity: Minimal configuration needed
  • Stability: Tooling rarely breaks
  • Cross-compilation: Works out of the box

Rust’s Strengths

  • Quality: rust-analyzer is best-in-class
  • Clippy: Teaching tool disguised as a linter
  • Documentation: Doc tests keep examples up to date
  • Version management: rustup is excellent
  • Error messages: Extremely helpful

Conclusion

Go’s tooling philosophy: “Make it simple, make it fast, get out of the way.”

Rust’s tooling philosophy: “Make it comprehensive, make it helpful, teach best practices.”

Quick Comparison

Tool Go Rust Winner
Installation Simple More complex (but better) Rust
Language Server gopls (good) rust-analyzer (excellent) Rust
Formatter gofmt rustfmt Tie
Linter vet + external clippy (built-in, amazing) Rust
Documentation godoc cargo doc (with tests) Rust
Cross-compile Built-in Requires setup Go
Build speed Very fast Slower Go
Version mgmt Manual rustup (built-in) Rust
Overall Faster, simpler Richer, more powerful Depends

For Go developers trying Rust: The tooling will surprise you. It’s actually better than Go’s in many ways (especially rust-analyzer and clippy). But builds are slower, and cross-compilation requires more setup.

Verdict: Go wins on speed and simplicity. Rust wins on richness and teaching you to write better code.

Tomorrow: Why the Rust compiler yells at you (and Go doesn’t)—and why that’s actually helpful.


Go to Rust Series: ← Hello World Comparison | Series Overview | The Rust Compiler →


Tooling verdict: Rust’s tools are surprisingly excellent. Clippy alone is worth learning Rust for.