Go to Rust Series: ← Hello World Comparison | Series Overview | The Rust Compiler →
Installation: First Impressions
Go: Download and Done
Installation:
- Go to golang.org/dl
- Download installer for your OS
- Run installer
- 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 managerrustc- Rust compilercargo- Package manager and build toolrust-docs- Offline documentationrust-std- Standard libraryrustfmt- Code formatterclippy- 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.0and thengo1.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:
- Install “Go” extension
- It installs
goplsautomatically - 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:
- Install “rust-analyzer” extension
- 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)
- Download and install Go (5 min)
- Install VS Code + Go extension (5 min)
- Install golangci-lint (2 min)
- Write code (immediately productive)
Total investment: < 30 minutes
Rust Developer Setup (1 hour)
- Install rustup (5 min)
- Install VS Code + rust-analyzer (5 min)
- Configure rust-analyzer (5 min)
- Learn cargo commands (15 min)
- Fight with the borrow checker (30 min)
- 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.