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ı.

8 dk okuma
ibrahimsql
1.507 kelime

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#

EksenGoRust
Öğrenme eğrisiDüşük-ortaYüksek
Concurrencygoroutine + channelasync/await + Send/Sync
Hata güvenliğierr handling explicitownership + Result/Option
Dağıtımstatic binarytagged musl/glibc static
EkosistemStd lib güçlücrates.io geniş
Debuggingpprof, tracegdb/lldb, cargo flamegraph
Derleme süresisaniyelerdakikalar (büyük projelerde)
Bağımlılık yönetimigo.modCargo.toml + Cargo.lock
Cross-compileGOOS=linux GOARCH=amd64--target üçlüsü

Ne Zaman Hangisi?#

DurumSeçimNeden
HTTP/JSON API, hızlı prototipGonet/http + tek binary, 20 dakikada ayağa kalkar
Paralel ağ servisi, binlerce istekGogoroutine ucuz ve channel ile koordinasyon kolay
CLI aracı, performans hassasRustownership ile uzun ömürlü process, hızlı startup
Bellek güvenliği kritik, zero-trust workerRustderleme zamanı garantisi, sıfır-cost abstraction
Dağıtım aracı, tek binaryGoCGO kapalı, static, kopyala-çalıştır
Kernel/driver/firmware seviyesiRustunsafe sınırı denetlenebilir, FFI gereksinimi var
Ekip küçük, bakım yükü düşük tutulmalıGodil yüzeyi küçük, "tek doğru yol" kültürü
Yüksek güvenli parser, fuzz edilen formatRustcargo-fuzz + ownership ile güvenli ayrıştırma
Aynı repo'da ikisiJSON/stdin sözleşmesidil 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ı#

  1. 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.
  2. Kilit dosyaları commit'le. Cargo.lock ve go.sum/go.mod takibe girsin; reproducible build için şart.
  3. Cross-compile matrisini CI'da dene. En azından linux/amd64 ve darwin/arm64 hedefleri; release workflow'u binary üretsin.
  4. IPC sözleşmesini versiyonla. Rust↔Go JSON şemasını schema.json olarak repo'da tut; her iki tarafta da validate et. Şema değişirse breaking change olarak etiketle.
  5. Dependabot / cargo-audit / govulncheck. Haftalık bağımlılık taraması; cargo audit ve govulncheck ./... CI step'i olsun.
  6. 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.
  7. Kod incelemesinde dikkat. Rust PR'larında unwrap() kullanımı ve unsafe bloku 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

---
Bu yazıyı paylaş:
TwitterLinkedInFacebook

Ne düşünüyorsun?

Tepki bırakarak geri bildirim ver

İlgili Yazılar