Rust ve Go: İkisi Birlikte Ne İşe Yarıyor
Rust ve Go'yu neden birlikte kullanıyorum: performans-ve-güvenlik ekseni, concurrency, dağıtım farkı ve pratik iş yükü paylaşımı.
Rust ve Go: İkisi Birlikte Ne İşe Yarıyor#
C/C++ ile başladım, bir sürü dili denedim, son yıllarda Rust ve Go ikilisiyle kaldım. İkisi de compiled, statik tip sistemi olan, hızlı diller; ama çözüm setleri ve hedef kitleleri farklı.
Kurulum#
İkisini de tek seferde ayağa kaldır:
# Rust - rustup ile curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh rustup default stable rustup component add clippy rustfmt # Go - resmi paket # macOS brew install go # Debian/Ubuntu sudo apt-get install -y golang-go # Arch sudo pacman -S go # Doğrulama rustc --version # rustc 1.8x.x go version # go1.23.x clippy-driver --version
Yeni proje iskeleti:
cargo new alpha --bin # Rust cd alpha && cargo run go mod init example.com/beta # Go # main.go oluştur, sonra: go run .
Neden İkisi?#
Go, concurrency'i ve hızlı derleme döngüsünü standart paket kütüphanesiyle yapar; ağ servisleri ve dağıtım araçları için bir zırh. Rust, güvenlik-çökme garantisini dil seviyesinde vererek kernel/tooling seviyesindeki işleri der; hata payını derleme zamanında değerlendirir. İkisi de "hızlı" diye nitelenir ama bu hızın ağırlık merkezi farklıdır: Go'da hızlı iterasyon ve runtime parallelism, Rust'ta önceden denetlenen bellek güvenliği ve sıfır-cost abstraction.
Ekosistem ve Üretkenlik#
Popüler dil demek, yüzlerce katkıcının yöneldiği tooling, paket sunucusu, CI şablonları demek. Go'nun go fmt, go vet, go test, pprof, trace araçları tek gövdeden çıkar; bir binary'yi net/http ile ayağa kaldırıp 20 dakikada deploy edebilirsin.
Rust'ta bu karşılık cargo ve crates.io ekosistemi, clippy, rustfmt, cargo audit ile gelir. Hazır crate'ler genelde üretim-ötesi görür ama bir kütüphane seçerken CFG ve versiyon takibi daha dikkat ister.
Birlikte Nasıl İş Bölüyorlar?#
Pratikte benim dağılımım şu:
- Rust: Performans ve güvenlik hassas araçlar, CLI'ler, bellek-dostu servisler, exit garantisi isteyen binary'ler.
- Go: HTTP/API servisleri, orchestrator'lar, hızlı prototipler, tek parça static binary ile dağıtılan dağıtım araçları.
Go'nun goroutine'leri binlerce eşzamanlı istek için açıklayıcı ve hafif; Rust'ta aynı şey tokio + async ile yapılır ama çoğu zaman Go'nun gömülü yaklaşımı projeyi baştan sona taşıyabilir.
Aynı işi ikisinde de görmek için paralel map örneği:
// Go package main import ( "fmt" "sync" ) func main() { jobs := []int{1, 2, 3, 4, 5} results := make([]int, len(jobs)) var wg sync.WaitGroup for i, j := range jobs { wg.Add(1) go func(i, j int) { defer wg.Done() results[i] = j * j }(i, j) } wg.Wait() fmt.Println(results) }
// Rust #[tokio::main] async fn main() { let jobs = vec![1, 2, 3, 4, 5]; let handles: Vec<_> = jobs .into_iter() .map(|j| tokio::spawn(async move { j * j })) .collect(); let mut results = Vec::new(); for h in handles { results.push(h.await.unwrap()); } println!("{:?}", results); }
Go tarafı tek goroutine + WaitGroup ile kapanır; Rust tarafı tokio::spawn ve join ister. İki taraf da 5 satır farkla aynı işi yapar, fark hatayı ve cancellation'ı nasıl ele aldığında ortaya çıkar.
Karşılıklı Çağrı (IPC) Örneği#
Bir projede ikisini gömmek yerine JSON/stdin üzerinden konuşturuyorum. Rust tarafı stdin'den satır listesi okur, toplam üretir:
// alpha/src/main.rs use std::io::{self, BufRead}; fn main() { let stdin = io::stdin(); let mut total: i64 = 0; for line in stdin.lock().lines() { let line = line.expect("stdin okunamadi"); if line.trim().is_empty() { continue; } let n: i64 = line.trim().parse().expect("sayi bekleniyordu"); total += n; } println!("{{\"sum\": {total}}}"); }
Go tarafı onu alt süreç olarak çağırır:
// beta/main.go package main import ( "fmt" "os/exec" "strings" ) func main() { cmd := exec.Command("./alpha") cmd.Stdin = strings.NewReader("10\n20\n30\n") out, err := cmd.Output() if err != nil { panic(err) } fmt.Printf("rust dedi ki: %s", out) }
Çıktı: rust dedi ki: {"sum": 60}. Sözleşme basit: stdin'e satır satır veri, stdout'tan tek satır JSON. Hata durumunda Rust exit code 1 dönsün, Go err'i yakalasın.
Makefile ile Alias'lar#
İkisini de aynı Makefile üzerinden derle/test et/lint'le:
RUST_DIR := alpha GO_DIR := beta .PHONY: build test lint run clean release build: cd $(RUST_DIR) && cargo build cd $(GO_DIR) && go build -o bin/beta . release: cd $(RUST_DIR) && cargo build --release cd $(GO_DIR) && CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/beta . test: cd $(RUST_DIR) && cargo test cd $(GO_DIR) && go test ./... lint: cd $(RUST_DIR) && cargo clippy -- -D warnings && cargo fmt --check cd $(GO_DIR) && go vet ./... && gofmt -l . run: cd $(GO_DIR) && go run . clean: cd $(RUST_DIR) && cargo clean rm -rf $(GO_DIR)/bin
make lint CI'da da koşar; clippy ve vet yakalamazsa gofmt/rustfmt format farkını yakalar. CGO_ENABLED=0 Go binary'sini tamamen static yapar; Rust tarafında musl hedefiyle aynısı:
rustup target add x86_64-unknown-linux-musl cargo build --release --target x86_64-unknown-linux-musl
Hızlı Karşılaştırma#
| Eksen | Go | Rust |
|---|---|---|
| Öğrenme eğrisi | Düşük-orta | Yüksek |
| Concurrency | goroutine + channel | async/await + Send/Sync |
| Hata güvenliği | err handling explicit | ownership + Result/Option |
| Dağıtım | static binary | tagged musl/glibc static |
| Ekosistem | Std lib güçlü | crates.io geniş |
| Debugging | pprof, trace | gdb/lldb, cargo flamegraph |
| Derleme süresi | saniyeler | dakikalar (büyük projelerde) |
| Bağımlılık yönetimi | go.mod | Cargo.toml + Cargo.lock |
| Cross-compile | GOOS=linux GOARCH=amd64 | --target üçlüsü |
Ne Zaman Hangisi?#
| Durum | Seçim | Neden |
|---|---|---|
| HTTP/JSON API, hızlı prototip | Go | net/http + tek binary, 20 dakikada ayağa kalkar |
| Paralel ağ servisi, binlerce istek | Go | goroutine ucuz ve channel ile koordinasyon kolay |
| CLI aracı, performans hassas | Rust | ownership ile uzun ömürlü process, hızlı startup |
| Bellek güvenliği kritik, zero-trust worker | Rust | derleme zamanı garantisi, sıfır-cost abstraction |
| Dağıtım aracı, tek binary | Go | CGO kapalı, static, kopyala-çalıştır |
| Kernel/driver/firmware seviyesi | Rust | unsafe sınırı denetlenebilir, FFI gereksinimi var |
| Ekip küçük, bakım yükü düşük tutulmalı | Go | dil yüzeyi küçük, "tek doğru yol" kültürü |
| Yüksek güvenli parser, fuzz edilen format | Rust | cargo-fuzz + ownership ile güvenli ayrıştırma |
| Aynı repo'da ikisi | JSON/stdin sözleşmesi | dil başına modül, tek binary şişmesi yok |
Basit kural: servis tarafı ağ işi ise Go, binary tarafı güvenlik/performans ise Rust. İkisini tek process'te gömme; arayüzü net tut.
PR Workflow Notları#
- CI'da iki dil için ayrı job. Rust:
cargo test,cargo clippy -- -D warnings,cargo fmt --check. Go:go test ./...,go vet ./...,gofmt -l. Biri kırmızıysa merge yok. - Kilit dosyaları commit'le.
Cargo.lockvego.sum/go.modtakibe girsin; reproducible build için şart. - Cross-compile matrisini CI'da dene. En azından
linux/amd64vedarwin/arm64hedefleri; release workflow'u binary üretsin. - IPC sözleşmesini versiyonla. Rust↔Go JSON şemasını
schema.jsonolarak repo'da tut; her iki tarafta da validate et. Şema değişirse breaking change olarak etiketle. - Dependabot / cargo-audit / govulncheck. Haftalık bağımlılık taraması;
cargo auditvegovulncheck ./...CI step'i olsun. - Commit mesajı disiplini.
feat(alpha):,fix(beta):önekiyle hangi modülün değiştiğini göster; küçük PR'lar iki dilde de derlemeyi hızlandırır. - Kod incelemesinde dikkat. Rust PR'larında
unwrap()kullanımı veunsafebloku yorum ister; Go PR'larında goroutine leak (WaitGroup unutulması) ve context iptali gözden kaçar.
Profilleme ve Hata Ayıklama#
İki dilin de üretimde işe yarayan profil araçları var; "yavaş" diyip tahmin etmek yerine ölç:
# Go - CPU profili go test -bench=. -cpuprofile=cpu.prof go tool pprof cpu.prof # pprof içinde: top, list main.main, web (graphviz gerekir) # Go - goroutine ve heap pprof -http=:8080 http://localhost:6060/debug/pprof/heap # Rust - flamegraph script'i cargo install flamegraph cargo flamegraph --bin alpha # perf.data üretir; tarayıcıda SVG flamegraph aç
Go'da bir HTTP servisi çalıştırıyorsan _ "net/http/pprof" import edip varsayılan mux'a bağla; /debug/pprof/ uçlarından canlı profil al. Rust tarafında pprof-rs veya tracing + tracing-flame ile aynı şeyi yapabilirsin ama kurulum bir adım fazla.
Trace seviyesinde zaman çizelgesi için:
go test -trace=trace.out go tool trace trace.out # Tarayıcıda goroutine zaman çizelgesi açılır
Bağımlılık Yönetimi#
İki ekosistem de güvenli güncelleme ister:
# Rust cargo install cargo-audit cargo audit # bilinen CVE taraması cargo update # semver uyumlu güncellemeler cargo outdated # (cargo-outdated paketi) eski crate'ler # Go go list -m -u all # güncellenebilir modüller go get -u=patch ./... # yalnızca patch yükseltmeleri go install golang.org/x/vuln/cmd/govulncheck@latest govulncheck ./...
CI'ya cargo audit ve govulncheck ./... step'i ekle; haftalık dependabot veya renovate ile otomatik PR açtır. Cargo.lock ve go.sum takibe girsin - kilit dosyası olmadan aynı commit iki makinede farklı davranır.
Major yükseltmelerde (Go 1.x'ten 1.y'ye, Rust edition değişiminde) önce ayrı bir branch'te derle, test et, sonra birleştir.
Önemli Kısım: Bağlılık ve Kanalizasyon#
Dil seçmek teknik karardan öte: uzun süreli projede aynı dile bağlı kalmak için o dili sevmen gerekir. Go'yu sevmemim sebebi basitlik ve hızlı deploy döngüsü; Rust'ı sevmemim sebebi hata güvenliği konusundaki rahatlık. Yine de bir projede ikisini birden gömmem: her dilin tutarlılığı kendi içinde kalır, gerektiğinde JSON/stdin üzerinden konuşurlar.
İlk günden cargo ve go hedeflerini tek Makefile'da toplamak, "hangi komutu çalıştıracağım" tartışmasını bitiriyor. make build && make test && make lint üçlüsü her gün aynı. Biri yeni katılınca README'deki o üç satır yeterli.
// RAPOR SONU Etiketler: Rust / Go / Dil Seçimi / Workflow
Ne düşünüyorsun?
Tepki bırakarak geri bildirim ver